Browse Source

init 新增机器人充电自动模拟数据任务

sptkw 1 year ago
parent
commit
40730cdd3f

+ 373 - 0
app/Command/StartCommand.php

@@ -0,0 +1,373 @@
+<?php
+declare(strict_types=1);
+
+namespace App\Command;
+
+use Swoole\Process;
+use Symfony\Component\Console\Command\Command;
+
+use Hyperf\Contract\ConfigInterface;
+use Hyperf\Contract\StdoutLoggerInterface;
+use Hyperf\Server\ServerFactory;
+use InvalidArgumentException;
+use Psr\Container\ContainerInterface;
+use Psr\EventDispatcher\EventDispatcherInterface;
+use Swoole\Runtime;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+use Hyperf\Command\Annotation\Command as HyperfCommand;
+
+//php bin/hyperf.php start -d //启动服务并进入后台模式
+//php bin/hyperf.php start -c //启动服务并清除 runtime/container 目录
+//php bin/hyperf.php start -w //启动服务并监控 app、config目录以及 .env 变化自动重启
+//php bin/hyperf.php start -w -p /bin/php //启动 watch 服务,参数 p 指定 php 安装目录
+//php bin/hyperf.php start -w -t 10  //启动 watch 服务,参数 t 指定 watch 时间间隔,单位秒
+//php bin/hyperf.php stop //停止服务
+//php bin/hyperf.php restart //重启服务
+//php bin/hyperf.php restart -c //重启服务并清除 runtime/container 目录
+/**
+ * @HyperfCommand()
+ */
+class StartCommand extends Command
+{
+    /**
+     * @var ContainerInterface
+     */
+    private $container;
+
+    /**
+     * @var SymfonyStyle
+     */
+    private $io;
+
+    /**
+     * @var int
+     */
+    private $interval;
+
+    /**
+     * @var bool
+     */
+    private $clear;
+
+    /**
+     * @var bool
+     */
+    private $daemonize;
+
+    /**
+     * @var string
+     */
+    private $php;
+
+    public function __construct(ContainerInterface $container)
+    {
+        $this->container = $container;
+        parent::__construct('start');
+    }
+
+    protected function configure()
+    {
+        $this
+            ->setDescription('Start hyperf servers.')
+            ->addOption('daemonize', 'd', InputOption::VALUE_OPTIONAL, 'swoole server daemonize', false)
+            ->addOption('clear', 'c', InputOption::VALUE_OPTIONAL, 'clear runtime container', false)
+            ->addOption('watch', 'w', InputOption::VALUE_OPTIONAL, 'watch swoole server', false)
+            ->addOption('interval', 't', InputOption::VALUE_OPTIONAL, 'interval time ( 1-15 seconds)', 3)
+            ->addOption('php', 'p', InputOption::VALUE_OPTIONAL, 'which php');
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output)
+    {
+        $this->io = new SymfonyStyle($input, $output);
+
+        $this->checkEnvironment($output);
+
+        $this->stopServer();
+
+        $this->clear = ($input->getOption('clear') !== false);
+
+        $this->daemonize = ($input->getOption('daemonize') !== false);
+
+        if ($input->getOption('watch') !== false) {
+
+            $this->interval = (int)$input->getOption('interval');
+            if ($this->interval < 0 || $this->interval > 15) {
+                $this->interval = 3;
+            }
+            if (!$this->php = $input->getOption('php')) {
+                if (!$this->php = exec('which php')) {
+                    $this->php = 'php';
+                }
+            }
+            $this->watchServer();
+        } else {
+            if ($this->clear) {
+                $this->clearRuntimeContainer();
+            }
+
+            $this->startServer();
+        }
+        return 0;
+    }
+
+    private function checkEnvironment(OutputInterface $output)
+    {
+        /**
+         * swoole.use_shortname = true       => string(1) "1"     => enabled
+         * swoole.use_shortname = "true"     => string(1) "1"     => enabled
+         * swoole.use_shortname = on         => string(1) "1"     => enabled
+         * swoole.use_shortname = On         => string(1) "1"     => enabled
+         * swoole.use_shortname = "On"       => string(2) "On"    => enabled
+         * swoole.use_shortname = "on"       => string(2) "on"    => enabled
+         * swoole.use_shortname = 1          => string(1) "1"     => enabled
+         * swoole.use_shortname = "1"        => string(1) "1"     => enabled
+         * swoole.use_shortname = 2          => string(1) "1"     => enabled
+         * swoole.use_shortname = false      => string(0) ""      => disabled
+         * swoole.use_shortname = "false"    => string(5) "false" => disabled
+         * swoole.use_shortname = off        => string(0) ""      => disabled
+         * swoole.use_shortname = Off        => string(0) ""      => disabled
+         * swoole.use_shortname = "off"      => string(3) "off"   => disabled
+         * swoole.use_shortname = "Off"      => string(3) "Off"   => disabled
+         * swoole.use_shortname = 0          => string(1) "0"     => disabled
+         * swoole.use_shortname = "0"        => string(1) "0"     => disabled
+         * swoole.use_shortname = 00         => string(2) "00"    => disabled
+         * swoole.use_shortname = "00"       => string(2) "00"    => disabled
+         * swoole.use_shortname = ""         => string(0) ""      => disabled
+         * swoole.use_shortname = " "        => string(1) " "     => disabled.
+         */
+        $useShortname = ini_get_all('swoole')['swoole.use_shortname']['local_value'];
+        $useShortname = strtolower(trim(str_replace('0', '', $useShortname)));
+        if (!in_array($useShortname, ['', 'off', 'false'], true)) {
+            $output->writeln('<error>ERROR</error> Swoole short name have to disable before start server, please set swoole.use_shortname = off into your php.ini.');
+            exit(0);
+        }
+    }
+
+
+    private function clearRuntimeContainer()
+    {
+        exec('rm -rf ' . BASE_PATH . '/runtime/container');
+    }
+
+    private function startServer()
+    {
+        $serverFactory = $this->container->get(ServerFactory::class)
+            ->setEventDispatcher($this->container->get(EventDispatcherInterface::class))
+            ->setLogger($this->container->get(StdoutLoggerInterface::class));
+
+        $serverConfig = $this->container->get(ConfigInterface::class)->get('server', []);
+        if (!$serverConfig) {
+            throw new InvalidArgumentException('At least one server should be defined.');
+        }
+
+        if ($this->daemonize) {
+            $serverConfig['settings']['daemonize'] = 1;
+            $this->io->success('swoole server start success.');
+        }
+
+        Runtime::enableCoroutine(true, swoole_hook_flags());
+
+        $serverFactory->configure($serverConfig);
+
+        $serverFactory->start();
+
+    }
+
+    private function stopServer()
+    {
+        $pidFile = BASE_PATH . '/runtime/hyperf.pid';
+        $pid = file_exists($pidFile) ? intval(file_get_contents($pidFile)) : false;
+        if ($pid && Process::kill($pid, SIG_DFL)) {
+            if (!Process::kill($pid, SIGTERM)) {
+                $this->io->error('old swoole server stop error.');
+                die();
+            }
+
+            while (Process::kill($pid, SIG_DFL)) {
+                sleep(1);
+            }
+        }
+    }
+
+    private function watchServer()
+    {
+        $this->io->note('start new swoole server ...');
+        $pid = $this->startProcess();
+
+        while ($pid > 0) {
+
+            $this->watch();
+
+            $this->io->note('restart swoole server ...');
+
+            $this->stopProcess($pid);
+
+            $pid = $this->startProcess();
+
+            sleep(1);
+        }
+    }
+
+    private function startProcess()
+    {
+        $this->clearRuntimeContainer();
+
+        $process = new Process(function (Process $process) {
+            $args = [BASE_PATH . '/bin/hyperf.php', 'start'];
+            if ($this->daemonize) {
+                $args[] = '-d';
+            }
+            $process->exec($this->php, $args);
+        });
+        return $process->start();
+    }
+
+    private function stopProcess(int $pid): bool
+    {
+        $this->io->text('stop old swoole server. pid:' . $pid);
+
+        $timeout = 15;
+        $startTime = time();
+
+        while (true) {
+            if ($ret = Process::wait(false) && $ret['pid'] == $pid) {
+                return true;
+            }
+            if (!Process::kill($pid, SIG_DFL)) {
+                return true;
+            }
+            if ((time() - $startTime) >= $timeout) {
+                $this->io->error('stop old swoole server timeout.');
+                return false;
+            }
+            Process::kill($pid, SIGTERM);
+            sleep(1);
+        }
+        return false;
+    }
+
+    private function monitorDirs(bool $recursive = false)
+    {
+        $dirs[] = BASE_PATH . '/app';
+        $dirs[] = BASE_PATH . '/config';
+
+        if ($recursive) {
+            foreach ($dirs as $dir) {
+                $dirIterator = new \RecursiveDirectoryIterator($dir);
+                $iterator = new \RecursiveIteratorIterator($dirIterator, \RecursiveIteratorIterator::SELF_FIRST);
+                /** @var \SplFileInfo $file */
+                foreach ($iterator as $file) {
+                    if ($file->isDir() && $file->getFilename() != '.' && $file->getFilename() != '..') {
+                        $dirs[] = $file->getPathname();
+                    }
+                }
+            }
+        }
+
+        return $dirs;
+    }
+
+    private function monitorFiles()
+    {
+        $files[] = BASE_PATH . '/.env';
+        return $files;
+    }
+
+    private function watch()
+    {
+        if (extension_loaded('inotify')) {
+            return $this->inotifyWatch();
+        } else {
+            return $this->fileWatch();
+        }
+    }
+
+    private function inotifyWatch()
+    {
+        $fd = inotify_init();
+        stream_set_blocking($fd, 0);
+
+        $dirs = $this->monitorDirs(true);
+        foreach ($dirs as $dir) {
+            inotify_add_watch($dir, IN_CLOSE_WRITE | IN_CREATE | IN_DELETE);
+        }
+        $files = $this->monitorFiles();
+        foreach ($files as $file) {
+            inotify_add_watch($file, IN_CLOSE_WRITE | IN_CREATE | IN_DELETE);
+        }
+
+        while (true) {
+            sleep($this->interval);
+            if (inotify_read($fd)) {
+                break;
+            }
+        }
+
+        fclose($fd);
+    }
+
+    private function fileWatch()
+    {
+        $dirs = $this->monitorDirs();
+        $files = $this->monitorFiles();
+        $inodeListOld = [];
+        $inodeListNew = [];
+        $isFrist = true;
+        while (true) {
+            foreach ($dirs as $dir) {
+                $dirIterator = new \RecursiveDirectoryIterator($dir);
+                $iterator = new \RecursiveIteratorIterator($dirIterator, \RecursiveIteratorIterator::LEAVES_ONLY);
+                /** @var \SplFileInfo $file */
+                foreach ($iterator as $file) {
+                    if ($file->isFile() && in_array(strtolower($file->getExtension()), ['env', 'php'])) {
+                        $inode = $file->getInode();
+                        $sign = $file->getFilename() . $file->getMTime();
+                        if ($isFrist) {
+                            $inodeListOld[$inode] = $sign;
+                        } else {
+                            // add new file || file changed
+                            if (!isset($inodeListOld[$inode]) || $inodeListOld[$inode] != $sign) {
+                                return true;
+                            } else {
+                                $inodeListNew[$inode] = $sign;
+                            }
+                        }
+                    }
+                }
+            }
+
+            foreach ($files as $key => $file) {
+                if (file_exists($file)) {
+                    $file = new \SplFileInfo($file);
+                    $inode = $file->getInode();
+                    $sign = $file->getFilename() . $file->getMTime();
+                    if ($isFrist) {
+                        $inodeListOld[$inode] = $sign;
+                    } else {
+                        // add new file || file changed
+                        if (!isset($inodeListOld[$inode]) || $inodeListOld[$inode] != $sign) {
+                            return true;
+                        } else {
+                            $inodeListNew[$inode] = $sign;
+                        }
+                    }
+                }
+            }
+
+            if ($isFrist) {
+                $isFrist = false;
+            } else {
+                // file remove
+                if (!empty(array_diff($inodeListOld, $inodeListNew))) {
+                    return true;
+                }
+            }
+
+            sleep($this->interval);
+        }
+    }
+
+}

+ 53 - 0
app/Command/StopCommand.php

@@ -0,0 +1,53 @@
+<?php
+declare(strict_types=1);
+namespace App\Command;
+
+//use App\Model\SystemWork;
+use Swoole\Process;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+use Hyperf\Command\Annotation\Command as HyperfCommand;
+/**
+ * @HyperfCommand()
+ */
+class StopCommand extends Command
+{
+    public function __construct()
+    {
+        parent::__construct('stop');
+    }
+
+    protected function configure()
+    {
+        $this->setDescription('Stop hyperf servers.');
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output)
+    {
+        $io = new SymfonyStyle($input, $output);
+        $pidFile = BASE_PATH . '/runtime/hyperf.pid';
+        $pid = file_exists($pidFile) ? intval(file_get_contents($pidFile)) : false;
+        if (!$pid) {
+            $io->note('swoole server pid is invalid.');
+            return -1;
+        }
+
+        if (!Process::kill($pid, SIG_DFL)) {
+            $io->note('swoole server process does not exist.');
+            return -1;
+        }
+
+        if (!Process::kill($pid, SIGTERM)) {
+            $io->error('swoole server stop error.');
+            return -1;
+        }
+
+        $io->success('swoole server stop success.');
+
+//        SystemWork::shutDown();
+        return 0;
+    }
+
+}

+ 344 - 0
app/Crontab/SckwChgrobotCrontab.php

@@ -0,0 +1,344 @@
+<?php
+declare(strict_types=1);
+
+namespace App\Crontab;
+
+
+use App\Model\RRobotAutoOrderModel;
+use App\Model\RTradeOrderModel;
+use App\Model\TEmployeeMemberModel;
+use App\Model\TEmployeeModel;
+use Carbon\Carbon;
+use Hyperf\Contract\StdoutLoggerInterface;
+use Hyperf\Crontab\Annotation\Crontab;
+use Hyperf\DbConnection\Db;
+use Hyperf\Di\Annotation\Inject;
+
+/**
+ * Desc:自动生成订单任务每天一次
+ * Author: Spt <2864934511@qq.com>
+ * Date: 2024-03-13 08:36
+ */
+#[Crontab(rule: "0 *\/1 * * *", name: "SckwChgrobot", callback: "execute", memo: "自动生成订单任务每天一次", enable: "isEnable")]
+class SckwChgrobotCrontab
+{
+//    /**
+//     * 定时任务唯一标识
+//     */
+//    protected const code = 'auto_order_crontab';
+
+    #[Inject]
+    private StdoutLoggerInterface $logger;
+
+    private static array $users = [
+        'oJdn-7JDOOCzlOgS6rE5CsXw_PbM',//pdy
+//        '',//
+    ];
+
+    /**
+     * 充电最低标准电量
+     * @var int
+     */
+    private static int $charge_minimum_power = 3;
+
+
+    /**
+     * Desc: 是否随开机启动
+     * Author: Spt <2864934511@qq.com>
+     * Date: 2024-03-12 16:08
+     * @return bool
+     */
+    public function isEnable(): bool
+    {
+        return true;
+    }
+
+    public function execute(): void
+    {
+        $this->logger->info(__CLASS__ . ' 自动生成订单任务开始 ' . datetime());
+        $this->task();
+    }
+
+    private function task(): bool
+    {
+
+        //插入测试员工 - ok
+        //INSERT INTO `t_employee`(`employee_id`, `system_type`, `login_name`, `login_pwd`, `open_id`, `actual_name`, `birthday`, `phone`, `remark`) VALUES (87, 5, '18589088034', '18589088034', 'oJdn-7JDOOCzlOgS6rE5CsXw_PbM', 'pdy', '2001-02-01 14:37:37', '18589088034', '测试号,不删除');
+        //插入测试员工会员卡 - ok
+        //INSERT INTO `r_member_order`(`id`, `open_id`, `type`, `user_id`, `order_no`, `member_no`, `end_time`, `create_time`, `update_time`, `status`, `del_flag`, `member_facility_id`) VALUES (NULL, 'oJdn-7JDOOCzlOgS6rE5CsXw_PbM', 1, 87, 'M2024031316352413', '202403131437082', '2024-03-15 16:35:24', '2024-03-08 16:35:24', '2024-03-08 16:35:24', 0, 0, NULL);
+        //INSERT INTO `r_employee_member`(`open_id`, `user_id`, `member_number`, `create_time`, `update_time`, `end_time`) VALUES ('oJdn-7JDOOCzlOgS6rE5CsXw_PbM', 87,'202403131437082','2024-03-13 14:34:01','2024-03-13 14:34:03','2029-04-01 14:34:05') ;
+        //
+
+
+        //获取所有任务
+        $list = RRobotAutoOrderModel::with(['robot', 'site', 'price'])
+            ->where('num', '<', 2)//执行次数0无限1一次超过1则不执行
+            ->where('del_flag', 0)
+            ->get();
+        if ($list->isNotEmpty()) {
+            //获取所有可用人员
+            $userLists = TEmployeeMemberModel::with(['user'])
+                ->whereIn('open_id', self::$users)//openid 范围查询
+                ->where('del_flag', 0)//是否删除0否 1是
+                ->get();
+            if ($userLists->isEmpty()) {
+                $this->logger->error(' 查无会员用户:' . json_encode(self::$users, JSON_UNESCAPED_UNICODE));
+                return false;
+            } else {
+                $userLists = $userLists->toArray();
+            }
+            foreach ($list as $robot) {
+                //本轮充放电概率确认
+                //充
+                $c_efficiency = $this->randApproximateRate($robot);
+//                //放
+//                $d_efficiency = $this->randApproximateRate($robot);
+                $this->logger->info($robot->id . '本轮充放电概率确认, 充:' . $c_efficiency);// . '  放:' . $d_efficiency
+
+                //总电量:
+                $total_power = $robot->full_charge;
+                $this->logger->info($robot->id . "总电量 :" . $total_power);
+
+                //本轮机器人充电和放电时间时长确认 满电量*波动概率 (单位:秒)
+                //充
+                $charging_c = ceil($total_power * $c_efficiency * 60);
+//                //放
+//                $efficiency_f = $total_power * $d_efficiency * 60;
+//                $this->logger->info($robot->id . "本轮机器人充电放电时间时长确认(单位:秒):" . $charging_c . ' ' . $efficiency_f);
+
+                //所差时段开始时间 和 所差时段结束时间 ,循环放电
+                if ($robot->num == 1) {
+                    $bad_start_periods_time = strtotime($robot->bad_start_periods);
+                    $bad_end_periods_time = strtotime($robot->bad_end_periods);
+                } else {
+                    $bad_start_periods_time = strtotime(date('Y-m-d') . $robot->bad_start_periods);
+                    $bad_end_periods_time = strtotime(date('Y-m-d') . $robot->bad_end_periods);
+                }
+
+                //电价
+                if (!$robot->price || empty($robot->price)) {
+                    $this->logger->info('注意当前充电站点无单价配置,将用默认配置:' . $robot->id);
+                    $site_price[] = [
+                        'site_id' => $robot->site_id,
+                        'start_time' => '00:00:00',
+                        'ent_time' => '23:59:59',
+                        'price' => 0,
+                        'serve_price' => 0,
+                    ];
+                } else {
+                    $site_price = $robot->price->toArray();
+                }
+
+                $is_charge = false;//机器人是否需要充电
+                //默认组装
+                $dat = [
+//                    "robot" => $robot->robot->imei,
+//                    "order" => "",
+//                    "user" => '',
+//                    "start_time" => $bad_start_periods_time,
+//                    "end_time" => "",
+//                    "time" => 0,//时长
+//                    "soc" => "",//电量占比
+//                    "billing" => "",
+//                    "c_type" => 1,//充电模式
+//                    "start_soc" => 0,//充电开始度数
+//                    "end_soc" => 0,//充电结束度数
+
+                    "user_id" => '',//用户id
+                    "open_id" => '',//微信appid
+                    "customer_phone" => '',//客户电话
+                    "type" => 2,//1预购订单2点卡充值订单
+                    "buy_order_id" => '',//预缴费订单id/点卡充值订单id(memberOrderId)
+                    "site_id" => $robot->site_id,//站点id
+                    "robot_id" => $robot->robot_id,//机器人id
+                    "order_no" => '',//系统订单号(必须)
+                    "charging_pile_id" => $robot->robot->imei,//充电桩id
+                    "pile_imei" => $robot->robot->imei,//终端 IMEI
+                    "charge_model" => 5,//充电模式(5 自然充满、10 按电\r\n量、15 按金额)
+//                    "plan_power"=>null,//计划充电量(kwh) 非自然充满模\r\n式必填
+//                    "plan_amount"=>null,//计划充电金额,自然充满模式则\r\n为用户余额
+                    "status" => 4,//订单状态(1进行中,2已结算,3未支付(预留),4完成)
+//                    "third_order_no"=>'',//三方订单号(记录 ID)
+                    "duration" => 0,//充电时长(分钟)向上取整-
+                    "voltage" => 220,//电压
+                    "electric_current" => 6,//电流
+                    "power" => 0,//充电量(kwh)-
+                    "amount" => 0,//订单金额-
+                    "charging_status" => 15,//充电状态(5: 待充电,10: 充电中,15: 已结束,20: 已取消)
+//                    "stop_reason"=>'',//结束原因
+//                    "remark"=>'',//备注
+//                    "seq"=>'',//三方下发序列号
+//                    "soc"=>'',//被充车辆 SOC 不一定有
+                    "create_time" => datetime(),//创建时间
+                    "update_time" => datetime(),//创建时间
+                ];
+                //第二步,根据所差时段开始时间 和 所差时段结束时间 ,循环放电
+
+                $start_time = $bad_start_periods_time;
+//                $end_time = $dat['start_time'];
+                $charging_interval = rand($robot->charging_interval_min * 60, $robot->charging_interval_max * 60);//随机间隔充电时间 单位(秒)
+                while (true) {
+                    $this->logger->info("循环充电");
+                    $data = $dat;
+
+                    $start_time += $charging_interval;//本次机器人放电时间,即车辆充电开始时间
+
+                    //本次充电结束时间大于预定时间段,则充电订单生成结束
+                    if ($start_time > $bad_end_periods_time) {
+                        break;
+                    }
+
+                    //获取此次充电量KWH
+                    $rand_soc = rand($robot->charging_time_start * 100, $robot->charging_time_end * 100) / 100;//乘除是为了创造小数 
+                    $this->logger->info($robot->id . '本轮获取此次充电量KWH:' . $rand_soc);
+
+                    //获取此次充电效率
+                    //本轮放电概率确认
+                    $lde = $this->randApproximateRate($robot);;
+                    $this->logger->info($robot->id . '本轮充放电概率确认, 放:' . $lde);
+
+                    //判断机器人电量是否足够此次放电,要不超出放电
+                    if ($rand_soc > $total_power) {
+                        $rand_soc = $total_power;
+                        //提示本次放完电机器人就需要充电了
+                        $is_charge = true;
+                    }
+
+                    //如果本次充电量小于3kwh,直接给机器人充电
+                    if ($rand_soc > self::$charge_minimum_power) {
+                        //获取本次充电时间 时长 = 电量 * 放电率
+                        $fc = floatval(bcmul((string)$rand_soc, (string)$lde, 2));
+                        $duration = ceil($fc);//所需分钟
+                        $end_time = $start_time + ceil($fc * 60);//所需秒钟
+                        //电费 = 站点规定当前时段(电费单价 + 服务单价)* 电量
+                        $amount = 0;
+                        //根据当前时段,判断电费单价和服务费单价
+                        //有配置就有配置单价,没有配置用默认
+                        $t_date_ymd = date('Y-m-d', intval($start_time));
+                        foreach ($site_price as $v) {
+                            $t_start_time = strtotime($t_date_ymd . ' ' . $v['start_time']);
+                            $t_ent_time = strtotime($t_date_ymd . ' ' . $v['ent_time']);
+                            if ($start_time >= $t_start_time && $t_start_time <= $t_ent_time) {
+                                $t_price = ($v['price'] == 0) ? $robot->default_electricity_bill : $v['price'];
+                                $t_serve_price = ($v['serve_price'] == 0) ? $robot->default_service_price : $v['serve_price'];
+                                $amount = floatval(bcmul((string)$rand_soc, bcadd((string)$t_price, (string)$t_serve_price, 2), 2));
+                            }
+                        }
+                        //如果没有计算,则用默认规定的单价
+                        if ($amount == 0) {
+                            $amount = floatval(bcmul((string)$rand_soc, bcadd((string)$robot->default_electricity_bill, (string)$robot->default_service_price, 2), 2));
+                        }
+
+                        //确认用户
+                        $this_user = $userLists[array_rand($userLists, 1)];
+                        $data['user_id'] = $this_user['user_id'];//
+                        $data['open_id'] = $this_user['open_id'];//
+                        $data['customer_phone'] = $this_user['user']['phone'];//
+                        $data['buy_order_id'] = $this_user['member_number'];//
+
+                        $data['order_no'] = 'TR' . $this->getPrefixData();//
+                        $data['duration'] = $duration;//
+                        $data['power'] = $rand_soc;//
+                        $data['amount'] = $amount;//
+                        $data['create_time'] = datetime($start_time);//
+                        $data['update_time'] = datetime($end_time);//
+
+                        //机器人容量减少
+                        $total_power = floatval(bcsub((string)$total_power, (string)$rand_soc, 2));
+                        //根据占比判断是否保存生成订单:
+                        if (rand(0, 100) > $robot->occupancy) {
+                            $this->logger->info("本轮跳过...");
+                            continue;
+                        } else {
+                            $res_data[] = $data;
+                        }
+                    }
+
+                    //判断是否需要充电
+                    if ($is_charge) {
+                        $start_time += $charging_c;
+                        //刷新 下轮充放电概率确认
+                        //充
+                        $c_efficiency = $this->randApproximateRate($robot);
+//                        //放
+//                        $d_efficiency = $this->randApproximateRate($robot);
+
+                        //总电量:
+                        $total_power = $robot->full_charge;
+
+                        //下轮机器人充电和放电时间时长确认 满电量*波动概率 (单位:秒)
+                        //充
+                        $charging_c = ceil($total_power * $c_efficiency * 60);
+//                        //放
+//                        $efficiency_f = $total_power * $d_efficiency * 60;
+
+                        $this->logger->info(PHP_EOL . PHP_EOL . $robot->id . '下轮充放电概率确认, 充:' . $c_efficiency);
+
+                        $is_charge = false;
+                    }
+                }
+
+                if (!empty($res_data)) {
+                    Db::beginTransaction();
+                    try {
+                        var_dump(json_encode($res_data, JSON_UNESCAPED_UNICODE));
+//                        $res = (new \App\Model\RTradeOrderModel)->save($res_data);
+                        $t_id = 0;
+                        foreach ($res_data as $da) {
+                            $t_id = RTradeOrderModel::insertGetId($da);
+                        }
+                        $this->logger->info(datetime() . ' ' . $robot->id . ' 生成成功: ' . ($t_id ? 'true' : 'false'));
+                        if ($robot->num == 1) {
+                            RRobotAutoOrderModel::where('id', $robot->id)->update(['num' => 2]);
+                        }
+                        Db::commit();
+                    } catch (\Exception $e) {
+                        Db::rollBack();
+                        $this->logger->error(datetime() . ' ' . $robot->id . '生成失败: ' . $e->getMessage());
+                    }
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Desc: 随机概率
+     * Author: Spt <2864934511@qq.com>
+     * Date: 2024-03-13 15:57
+     * @param $robot
+     * @return float|int
+     */
+    private function randApproximateRate($robot): float|int
+    {
+        return (rand(intval($robot->charging_efficiency_min * 100), intval($robot->charging_efficiency_max * 100))) / 100;
+    }
+
+    /**
+     * Desc: 自动生成前缀
+     * Author: Spt <2864934511@qq.com>
+     * Date: 2024-03-12 15:41
+     * @param string $prefix
+     * @return string
+     */
+    private function getPrefixData(string $prefix = ''): string
+    {
+        return $prefix . date("YmdHis") . rand(0, 9999);
+    }
+
+    /**
+     * Desc: 格式化时间格式为: *分*秒
+     * Author: Spt <2864934511@qq.com>
+     * Date: 2024-03-13 16:39
+     * @param $seconds
+     * @return string
+     */
+    private function convertSecondsToMinutesAndSeconds($seconds): string
+    {
+        $minutes = intval($seconds / 60);
+        $remainingSeconds = $seconds % 60;
+        $paddedSeconds = str_pad((string)$remainingSeconds, 2, '0', STR_PAD_LEFT); // 在左侧用0填充,直到长度为2
+        return $minutes . '分' . $paddedSeconds . '秒';
+    }
+
+}

+ 33 - 0
app/Listener/FailToExecuteCrontabListener.php

@@ -0,0 +1,33 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Listener;
+
+use Hyperf\Crontab\Event\FailToExecute;
+use Hyperf\Event\Annotation\Listener;
+use Psr\Container\ContainerInterface;
+use Hyperf\Event\Contract\ListenerInterface;
+
+/**
+ * 监测定时任务执行失败
+ */
+#[Listener]
+class FailToExecuteCrontabListener implements ListenerInterface
+{
+    public function __construct(protected ContainerInterface $container)
+    {
+    }
+
+    public function listen(): array
+    {
+        return [
+            FailToExecute::class,
+        ];
+    }
+
+    public function process(object $event): void
+    {
+        var_dump("定时任务执行失败:", $event->crontab->getName(), $event->throwable->getMessage());
+    }
+}

+ 68 - 0
app/Model/RRobotAutoOrderModel.php

@@ -0,0 +1,68 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Model;
+
+
+/**
+ * 充电站点订单自动补充任务
+ */
+class RRobotAutoOrderModel extends Model
+{
+    /**
+     * The connection name for the model.
+     */
+    protected ?string $connection = 'robot';
+
+    /**
+     * The table associated with the model.
+     */
+    protected ?string $table = 'r_robot_auto_order';
+
+    public bool $timestamps = true;
+
+    /**
+     * The name of the "created at" column.
+     *
+     * @var null|string
+     */
+    public const CREATED_AT = 'create_time';
+
+    /**
+     * The name of the "updated at" column.
+     *
+     * @var null|string
+     */
+    public const UPDATED_AT = 'update_time';
+
+    /**
+     * The attributes that are mass assignable.
+     */
+    protected array $fillable = [];
+
+    /**
+     * The attributes that should be cast to native types.
+     */
+    protected array $casts = [];
+
+    //对应机器人
+    public function robot(): \Hyperf\Database\Model\Relations\HasOne
+    {
+        return $this->hasOne(RRobotModel::class, 'id', 'robot_id')->where('del_flag', 0);
+    }
+
+    //对应站点
+    public function site(): \Hyperf\Database\Model\Relations\HasOne
+    {
+        return $this->hasOne(RRobotModel::class, 'id', 'site_id')->where('del_flag', 0);
+    }
+
+    //对应站点单价
+    public function price(): \Hyperf\Database\Model\Relations\HasMany
+    {
+        return $this->hasMany(RSitePriceModel::class, 'site_id', 'site_id')->where('del_flag', 0);
+    }
+
+
+}

+ 47 - 0
app/Model/RRobotModel.php

@@ -0,0 +1,47 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Model;
+
+/**
+ * 机器人信息表
+ */
+class RRobotModel extends Model
+{
+    /**
+     * The connection name for the model.
+     */
+    protected ?string $connection = 'robot';
+
+    /**
+     * The table associated with the model.
+     */
+    protected ?string $table = 'r_robot';
+
+    public bool $timestamps = true;
+
+    /**
+     * The name of the "created at" column.
+     *
+     * @var null|string
+     */
+    public const CREATED_AT = 'create_time';
+
+    /**
+     * The name of the "updated at" column.
+     *
+     * @var null|string
+     */
+    public const UPDATED_AT = 'update_time';
+
+    /**
+     * The attributes that are mass assignable.
+     */
+    protected array $fillable = [];
+
+    /**
+     * The attributes that should be cast to native types.
+     */
+    protected array $casts = [];
+}

+ 47 - 0
app/Model/RRobotSiteModel.php

@@ -0,0 +1,47 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Model;
+
+/**
+ * 机器人站点关联表
+ */
+class RRobotSiteModel extends Model
+{
+    /**
+     * The connection name for the model.
+     */
+    protected ?string $connection = 'robot';
+
+    /**
+     * The table associated with the model.
+     */
+    protected ?string $table = 'r_robot_site';
+
+    public bool $timestamps = true;
+
+    /**
+     * The name of the "created at" column.
+     *
+     * @var null|string
+     */
+    public const CREATED_AT = 'create_time';
+
+    /**
+     * The name of the "updated at" column.
+     *
+     * @var null|string
+     */
+    public const UPDATED_AT = 'update_time';
+
+    /**
+     * The attributes that are mass assignable.
+     */
+    protected array $fillable = [];
+
+    /**
+     * The attributes that should be cast to native types.
+     */
+    protected array $casts = [];
+}

+ 47 - 0
app/Model/RSiteModel.php

@@ -0,0 +1,47 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Model;
+
+/**
+ * 机器人站点表
+ */
+class RSiteModel extends Model
+{
+    /**
+     * The connection name for the model.
+     */
+    protected ?string $connection = 'robot';
+
+    /**
+     * The table associated with the model.
+     */
+    protected ?string $table = 'r_site';
+
+    public bool $timestamps = true;
+
+    /**
+     * The name of the "created at" column.
+     *
+     * @var null|string
+     */
+    public const CREATED_AT = 'create_time';
+
+    /**
+     * The name of the "updated at" column.
+     *
+     * @var null|string
+     */
+    public const UPDATED_AT = 'update_time';
+
+    /**
+     * The attributes that are mass assignable.
+     */
+    protected array $fillable = [];
+
+    /**
+     * The attributes that should be cast to native types.
+     */
+    protected array $casts = [];
+}

+ 47 - 0
app/Model/RSitePriceModel.php

@@ -0,0 +1,47 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Model;
+
+/**
+ * 机器人站点价格表
+ */
+class RSitePriceModel extends Model
+{
+    /**
+     * The connection name for the model.
+     */
+    protected ?string $connection = 'robot';
+
+    /**
+     * The table associated with the model.
+     */
+    protected ?string $table = 'r_site_price';
+
+    public bool $timestamps = true;
+
+    /**
+     * The name of the "created at" column.
+     *
+     * @var null|string
+     */
+    public const CREATED_AT = 'create_time';
+
+    /**
+     * The name of the "updated at" column.
+     *
+     * @var null|string
+     */
+    public const UPDATED_AT = 'update_time';
+
+    /**
+     * The attributes that are mass assignable.
+     */
+    protected array $fillable = [];
+
+    /**
+     * The attributes that should be cast to native types.
+     */
+    protected array $casts = [];
+}

+ 47 - 0
app/Model/RTradeOrderModel.php

@@ -0,0 +1,47 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Model;
+
+/**
+ * 销售订单表
+ */
+class RTradeOrderModel extends Model
+{
+    /**
+     * The connection name for the model.
+     */
+    protected ?string $connection = 'robot';
+
+    /**
+     * The table associated with the model.
+     */
+    protected ?string $table = 'r_trade_order';
+
+    public bool $timestamps = true;
+
+    /**
+     * The name of the "created at" column.
+     *
+     * @var null|string
+     */
+    public const CREATED_AT = 'create_time';
+
+    /**
+     * The name of the "updated at" column.
+     *
+     * @var null|string
+     */
+    public const UPDATED_AT = 'update_time';
+
+    /**
+     * The attributes that are mass assignable.
+     */
+    protected array $fillable = ['id', 'user_id', 'open_id', 'customer_phone', 'type', 'buy_order_id', 'site_id', 'robot_id', 'order_no', 'charging_pile_id', 'pile_imei', 'charge_model', 'plan_power', 'plan_amount', 'status', 'third_order_no', 'duration', 'voltage', 'electric_current', 'power', 'amount', 'charging_status', 'stop_reason', 'remark', 'seq', 'soc', 'create_time', 'update_time', 'del_flag'];
+
+    /**
+     * The attributes that should be cast to native types.
+     */
+    protected array $casts = [];
+}

+ 56 - 0
app/Model/TEmployeeMemberModel.php

@@ -0,0 +1,56 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Model;
+
+
+/**
+ * 员工充值后的会员表
+ */
+class TEmployeeMemberModel extends Model
+{
+    /**
+     * The connection name for the model.
+     */
+    protected ?string $connection = 'robot';
+
+    /**
+     * The table associated with the model.
+     */
+    protected ?string $table = 'r_employee_member';
+
+    public bool $timestamps = true;
+
+    /**
+     * The name of the "created at" column.
+     *
+     * @var null|string
+     */
+    public const CREATED_AT = 'create_time';
+
+    /**
+     * The name of the "updated at" column.
+     *
+     * @var null|string
+     */
+    public const UPDATED_AT = 'update_time';
+
+    /**
+     * The attributes that are mass assignable.
+     */
+    protected array $fillable = [];
+
+    /**
+     * The attributes that should be cast to native types.
+     */
+    protected array $casts = [];
+
+
+    public function user()
+    {
+        return $this->hasOne(TEmployeeModel::class, 'employee_id', 'user_id');
+    }
+
+
+}

+ 49 - 0
app/Model/TEmployeeModel.php

@@ -0,0 +1,49 @@
+<?php
+
+declare(strict_types=1);
+
+namespace App\Model;
+
+
+
+/**
+ * 员工表
+ */
+class TEmployeeModel extends Model
+{
+    /**
+     * The connection name for the model.
+     */
+    protected ?string $connection = 'robot';
+
+    /**
+     * The table associated with the model.
+     */
+    protected ?string $table = 't_employee';
+
+    public bool $timestamps = true;
+
+    /**
+     * The name of the "created at" column.
+     *
+     * @var null|string
+     */
+    public const CREATED_AT = 'create_time';
+
+    /**
+     * The name of the "updated at" column.
+     *
+     * @var null|string
+     */
+    public const UPDATED_AT = 'update_time';
+
+    /**
+     * The attributes that are mass assignable.
+     */
+    protected array $fillable = [];
+
+    /**
+     * The attributes that should be cast to native types.
+     */
+    protected array $casts = [];
+}

+ 35 - 0
config/autoload/common.php

@@ -0,0 +1,35 @@
+<?php
+declare(strict_types=1);
+/**
+ * Desc: 公共方法
+ * Author: Spt <2864934511@qq.com>
+ * Date: 2024-03-13 15:11
+ */
+
+if (!function_exists('datetime')) {
+    /**
+     * Generate the URL to a named route.
+     *
+     * @param null $time
+     * @return string
+     */
+    function datetime($time = null): string
+    {
+        $time = $time ?: time();
+        return date("Y-m-d H:i:s", (int)$time);
+    }
+}
+
+
+if (!function_exists('getMillisecond')) {
+
+    /**
+     * Notes:获取毫秒
+     * @return string
+     */
+    function getMillisecond(): string
+    {
+        list($s1, $s2) = explode(' ', microtime());
+        return sprintf('%.0f', (floatval($s1) + floatval($s2)) * 1000);
+    }
+}

+ 16 - 0
config/autoload/crontab.php

@@ -0,0 +1,16 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * This file is part of Hyperf.
+ *
+ * @link     https://www.hyperf.io
+ * @document https://hyperf.wiki
+ * @contact  group@hyperf.io
+ * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
+ */
+return [
+    'enable' => true,
+    'crontab' => [
+    ],
+];