diff --git a/app/command/AssignInquiryCommand.php b/app/command/AssignInquiryCommand.php new file mode 100644 index 0000000..b33b482 --- /dev/null +++ b/app/command/AssignInquiryCommand.php @@ -0,0 +1,145 @@ + $v > 0 + )); + + if (empty($userIds)) { + throw new BusinessException('SALES_USER_IDS 业务员列表不能为空,请先在命令中配置.'); + } + + // 取出“待分配”的询盘(你可以根据自己系统调整条件) + // 当前策略:status = 0 且 recipient = 0 的视为未分配 + $inquiries = Db::table('inquiry_log') + ->where('enter_user_id', 186) + ->where('channel', 10) + ->where('status', 0) + ->where('recipient', 0) + ->where('id', '>', 34426) + ->orderBy('id') + ->limit(static::BATCH_LIMIT) + ->get(); + + if ($inquiries->isEmpty()) { + $output->writeln('没有需要分配的询盘(status=0 且 recipient=0)'); + return self::SUCCESS; + } + + $now = time(); + $count = 0; + + foreach ($inquiries as $row) { + $inquiryId = (int)$row->id; + + // 轮询取出一个业务员 ID + $recipientUserId = $this->nextRecipientId($userIds, static::ROUND_ROBIN_GROUP_KEY); + + // 对每一条记录单独事务,避免单条异常拖垮全部 + Db::transaction(function () use ($inquiryId, $recipientUserId, $now, $output, &$count) { + // 1)更新 inquiry_log + $affected = Db::table('inquiry_log') + ->where('id', $inquiryId) + ->update([ + 'recipient' => $recipientUserId, + 'status' => 2, + 'r_time' => $now, + 't_time' => $now, + 'updated_at' => $now, + ]); + + if ($affected === 0) { + // 被其他进程抢先更新也有可能,这里只打印提示不中断整个批次 + $output->writeln("询盘 {$inquiryId} 未更新(可能已被处理)"); + return; + } + + // 2)构造队列 payload(后续 CRM 消费时至少知道 ID) + $res = [ + 'id' => $inquiryId, + 'recipient' => $recipientUserId, + 'dispatched_at' => $now, + 'source' => 'fb_leads_inquiry', + ]; + + // 3)推送到 CRM 队列 + RedisQueueRedis::send('inquiry-to-crm-new', $res); + + $count++; + $output->writeln("已分配询盘 {$inquiryId} 给业务员 {$recipientUserId},并推送到队列"); + }); + } + + $output->writeln("本次共分配成功 {$count} 条询盘"); + + return self::SUCCESS; + } + + /** + * 轮询获取“下一位业务员 ID” + * + * @param int[] $userIds + * @param string $groupKey + * @return int + * @throws BusinessException + */ + protected function nextRecipientId(array $userIds, string $groupKey = 'default'): int + { + $userIds = array_values(array_filter(array_map('intval', $userIds), fn($v) => $v > 0)); + if (empty($userIds)) { + throw new BusinessException('轮询分配失败:业务员列表为空'); + } + + $count = count($userIds); + $key = 'inquiry:assign:round_robin:' . $groupKey; + + // Redis INCR 是原子操作,适合并发下使用 + $val = Redis::incr($key); // 第一次会从 0 变成 1 + + // 换算为从 0 开始的下标 + $index = ($val - 1) % $count; + + return $userIds[$index]; + } +} diff --git a/app/command/FbLeadToInquiryCommand.php b/app/command/FbLeadToInquiryCommand.php new file mode 100644 index 0000000..36ec869 --- /dev/null +++ b/app/command/FbLeadToInquiryCommand.php @@ -0,0 +1,164 @@ +where('sync_status', 0) + ->get(); + + if ($leads->isEmpty()) { + $output->writeln('没有需要同步的 fb_leads(sync_status = 0)'); + return self::SUCCESS; + } + + $now = time(); + + Db::transaction(function () use ($leads, $now, $output) { + $insert = []; + $ids = []; + + foreach ($leads as $lead) { + $leadArr = (array)$lead; + $insert[] = $this->mapLeadToInquiry($leadArr, $now); + // 假设 fb_leads 的主键字段名为 id + $ids[] = $leadArr['id']; + } + + if ($insert) { + // 1) 写入 inquiry_log + Db::table('inquiry_log')->insert($insert); + + // 2) 回写 fb_leads 的同步状态 + Db::table('fb_leads') + ->whereIn('id', $ids) + ->update([ + 'sync_status' => 1, + 'updated_at' => $now, + ]); + + $output->writeln('已导入 ' . count($insert) . ' 条数据到 inquiry_log,并更新 fb_leads.sync_status = 1'); + } + }); + + return self::SUCCESS; + } + + /** + * 将一条 fb_leads 记录映射为 inquiry_log 记录 + */ + protected function mapLeadToInquiry(array $lead, int $now): array + { + // 联系方式 + $email = (string)($lead['email'] ?? ''); + $tel = (string)($lead['phone'] ?? ''); + $wa = $tel; // 先用电话号码当 WhatsApp + + // 根据电话或 WhatsApp 区号识别国家 + $countryId = PhoneCountryHelper::detectCountryId($tel ?: $wa); + + // 所有默认值按你的要求设置 + $data = [ + 'order_no' => $this->orderNum(), + 'enter_user_id' => static::DEFAULT_ENTER_USER_ID, + 'recipient' => 0, + 'quality' => 2, + 'channel' => static::DEFAULT_CHANNEL, + + 'email' => $email, + 'tel' => $tel, + 'whatsapp' => $wa, + 'country' => $countryId ?: 0, + 'contacts' => (string)($lead['full_name'] ?? ''), + + 'content' => $this->buildContentHtml($lead), + + 'status' => 0, + 'r_time' => 0, + 't_time' => 0, + 'sync_crm' => 0, + + 'created_at' => $now, + 'updated_at' => $now, + ]; + + return $data; + } + + /** + * 生成订单号 + */ + protected function orderNum(): string + { + return 'SN' . date('Ymd') . str_pad(mt_rand(1, 99999), 5, '0', STR_PAD_LEFT); + } + + /** + * 根据 fb_leads 的字段拼出富文本内容 + */ + protected function buildContentHtml(array $lead): string + { + $e = function ($v) { + return htmlspecialchars((string)$v, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); + }; + + $lines = []; + + if (!empty($lead['car_model'])) { + $lines[] = '

意向车型:' . $e($lead['car_model']) . '

'; + } + if (!empty($lead['car_quantity']) || !empty($lead['car_quantity_text'])) { + $qty = $lead['car_quantity'] ?? $lead['car_quantity_text'] ?? ''; + $lines[] = '

意向数量:' . $e($qty) . '

'; + } + if (!empty($lead['favorite_color'])) { + $lines[] = '

意向颜色:' . $e($lead['favorite_color']) . '

'; + } + if (!empty($lead['usage_type'])) { + $lines[] = '

用途:' . $e($lead['usage_type']) . '

'; + } + if (!empty($lead['seat_preference'])) { + $lines[] = '

座位数偏好:' . $e($lead['seat_preference']) . '

'; + } + + $lines[] = '
'; + $lines[] = '

广告与表单信息:

'; + $lines[] = '

平台:' . $e($lead['platform'] ?? '') . '

'; + $lines[] = '

Campaign:' . $e($lead['campaign_name'] ?? '') . ' (#' . $e($lead['campaign_id'] ?? '') . ')

'; + $lines[] = '

Adset:' . $e($lead['adset_name'] ?? '') . ' (#' . $e($lead['adset_id'] ?? '') . ')

'; + $lines[] = '

Ad:' . $e($lead['ad_name'] ?? '') . ' (#' . $e($lead['ad_id'] ?? '') . ')

'; + $lines[] = '

Form:' . $e($lead['form_name'] ?? '') . ' (#' . $e($lead['form_id'] ?? '') . ')

'; + + $lines[] = '
'; + $lines[] = '

Meta 信息:

'; + $lines[] = '

Lead ID:' . $e($lead['fb_lead_id'] ?? '') . '

'; + $lines[] = '

FB 创建时间原始值:' . $e($lead['fb_created_time'] ?? '') . '

'; + + if (!empty($lead['inbox_url'])) { + $url = $e($lead['inbox_url']); + $lines[] = '

Inbox 链接:' . $url . '

'; + } + + return implode("\n", $lines); + } +} diff --git a/app/command/TbCommand.php b/app/command/TbCommand.php new file mode 100644 index 0000000..9f505a4 --- /dev/null +++ b/app/command/TbCommand.php @@ -0,0 +1,256 @@ +addOption( + 'file', + null, + InputOption::VALUE_OPTIONAL, + '要导入的单个 Excel 文件名(相对 public/excel 的路径,或绝对路径)' + ) + ->addOption( + 'dir', + null, + InputOption::VALUE_OPTIONAL, + 'Excel 目录(相对 public/ 的路径)', + 'excel' // 默认就是 public/excel + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $fileOption = $input->getOption('file'); + $dirOption = $input->getOption('dir') ?? 'excel'; + + // 基准目录:public/{$dirOption} + $baseDir = rtrim(public_path($dirOption), DIRECTORY_SEPARATOR); + + if ($fileOption) { + // 如果传的是绝对路径,直接用;否则认为是相对 baseDir 的文件名 + if ($this->isAbsolutePath($fileOption)) { + $filePath = $fileOption; + } else { + $filePath = $baseDir . DIRECTORY_SEPARATOR . $fileOption; + } + + $output->writeln("开始导入单个文件: {$filePath}"); + + $this->importOneFile($filePath, $output); + } else { + // 不指定 file 时,默认导入目录下所有 .xlsx + if (!is_dir($baseDir)) { + throw new BusinessException("目录不存在:{$baseDir}"); + } + + $pattern = $baseDir . DIRECTORY_SEPARATOR . '*.xlsx'; + $files = glob($pattern); + + if (empty($files)) { + $output->writeln("目录 {$baseDir} 下没有找到 .xlsx 文件"); + return self::SUCCESS; + } + + $output->writeln("开始批量导入目录: {$baseDir}"); + foreach ($files as $filePath) { + $output->writeln(" -> 文件:{$filePath}"); + try { + $this->importOneFile($filePath, $output); + $output->writeln(" 导入成功"); + } catch (\Throwable $e) { + // 某个文件失败,不影响后面的文件 + $output->writeln(" 导入失败:{$e->getMessage()}"); + } + } + } + + return self::SUCCESS; + } + + /** + * 导入单个 Excel 文件 + */ + protected function importOneFile(string $filePath, OutputInterface $output): void + { + if (!is_file($filePath)) { + throw new BusinessException("文件不存在:{$filePath}"); + } + + $spreadsheet = IOFactory::load($filePath); + $sheet = $spreadsheet->getActiveSheet(); + $rows = $sheet->toArray(null, true, true, true); + + if (empty($rows)) { + throw new BusinessException('Excel 内容为空:' . $filePath); + } + + // 取第一行表头 + $headerRow = array_shift($rows); + + // 表头映射器 + $mapper = new FbLeadHeaderMapper(); + + // 如果有未知表头,这里会直接抛异常中断当前文件 + $colToField = $mapper->buildColumnFieldMap($headerRow); + + $insertData = []; + $now = time(); + + foreach ($rows as $row) { + $item = []; + + foreach ($colToField as $colIndex => $field) { + $raw = $row[$colIndex] ?? null; + + switch ($field) { + // 1) is_organic: Excel 里是 true/false,这里转成 0/1 + case 'is_organic': + $item['is_organic'] = $this->normalizeIsOrganic($raw); + break; + + // 2) fb_created_time: 统一转成 Y-m-d H:i:s + case 'fb_created_time': + $item['fb_created_time'] = $this->normalizeFbCreatedTime($raw); + break; + + default: + if (is_string($raw)) { + $raw = trim($raw); + } + $item[$field] = $raw; + break; + } + } + + // 补充公共字段 + $item['source_file'] = basename($filePath); + $item['created_at'] = $now; + $item['updated_at'] = $now; + + // 整行都空就跳过 + $allNull = true; + foreach ($item as $v) { + if ($v !== null && $v !== '') { + $allNull = false; + break; + } + } + if ($allNull) { + continue; + } + + $insertData[] = $item; + } + + if ($insertData) { + Db::table('fb_leads')->insert($insertData); + } else { + $output->writeln(" 文件 {$filePath} 没有有效数据行"); + } + } + + /** + * 将 Excel 里的 is_organic 值转换为 0/1 + */ + protected function normalizeIsOrganic($value): int + { + if ($value === null) { + return 0; + } + + $v = strtolower(trim((string)$value)); + + // 认为这些是 “true” + $trueValues = ['1', 'true', 'yes', 'y']; + + return in_array($v, $trueValues, true) ? 1 : 0; + } + + /** + * 统一处理 fb_created_time + * Excel 目前是类似 '12/8/25',这里尽量转成 'Y-m-d H:i:s' + * 解析失败时返回 null + */ + protected function normalizeFbCreatedTime($value): ?string + { + if ($value === null || $value === '') { + return null; + } + + if ($value instanceof \DateTimeInterface) { + return $value->format('Y-m-d H:i:s'); + } + + $v = trim((string)$value); + + $formats = [ + 'Y-m-d H:i:s', + 'Y-m-d', + 'n/j/y', // 12/8/25 + 'n/j/Y', + 'd/m/Y', + 'd.m.Y', + ]; + + foreach ($formats as $fmt) { + $dt = \DateTime::createFromFormat($fmt, $v); + if ($dt !== false) { + return $dt->format('Y-m-d H:i:s'); + } + } + + // 纯数字当时间戳试一下 + if (is_numeric($v) && strlen($v) >= 9 && strlen($v) <= 11) { + $ts = (int)$v; + if ($ts > 0) { + return date('Y-m-d H:i:s', $ts); + } + } + + return null; + } + + /** + * 判断是否为绝对路径(简单兼容 Windows / Linux) + */ + protected function isAbsolutePath(string $path): bool + { + // Windows: X:\ 或 \\server\share + if (preg_match('/^[a-zA-Z]:\\\\/', $path) || str_starts_with($path, '\\\\')) { + return true; + } + + // *nix: / 开头 + return str_starts_with($path, '/'); + } +} + +/* + +php webman tb-db +# 等价于 php webman tb-db --dir=excel +# 会遍历 public/excel/*.xlsx 逐个导入 + +# 相对路径(相对 public/excel) +php webman tb-db --file="BYD tang_Leads_2025-12-04_2025-12-09.xlsx" + +# 或给绝对路径 +php webman tb-db --file="/www/wwwroot/xxx/public/excel/BYD tang_Leads_2025-12-04_2025-12-09.xlsx" + +*/ \ No newline at end of file diff --git a/app/controller/IndexController.php b/app/controller/IndexController.php index c6489a3..1a5d257 100644 --- a/app/controller/IndexController.php +++ b/app/controller/IndexController.php @@ -9,6 +9,11 @@ use support\Log; class IndexController { + public function index() + { + return 22; + } + public function form(Request $request, int $id) { try { diff --git a/app/controller/TransmitController.php b/app/controller/TransmitController.php new file mode 100644 index 0000000..e96e283 --- /dev/null +++ b/app/controller/TransmitController.php @@ -0,0 +1,35 @@ +fetchForms(); + + $result = $client->fetchEntries(263, 0, 10); + + return json($result); + } catch (\Throwable $th) { + Log::info('Data:'.date('Y-m-d H:i:s').':'.$th->getMessage().'|'.$th->getLine().'|'.$th->getFile()); + } + } + +} \ No newline at end of file diff --git a/app/inquery/controller/InqueryController.php b/app/inquery/controller/InqueryController.php new file mode 100644 index 0000000..0ee0b0e --- /dev/null +++ b/app/inquery/controller/InqueryController.php @@ -0,0 +1,61 @@ +getActiveSheet(); + $rows = $sheet->toArray(null, true, true, true); + + if (empty($rows)) { + throw new BusinessException('Excel 内容为空'); + } + + // 取第一行表头 + $headerRow = array_shift($rows); + + // 实例化映射器 + $mapper = new FbLeadHeaderMapper(); + + // ① 如果有未知表头,会在这里抛出异常并终止处理 + $colToField = $mapper->buildColumnFieldMap($headerRow); + + // ② 开始处理数据行 + $insertData = []; + $now = time(); + + foreach ($rows as $row) { + $item = []; + + foreach ($colToField as $colIndex => $field) { + $item[$field] = $row[$colIndex] ?? null; + } + + // 补充你需要的其他字段 + $item['source_file'] = basename($filePath); + $item['created_at'] = $now; + $item['updated_at'] = $now; + + $insertData[] = $item; + } + + // ③ 批量入库(根据你的实际需求做去重等逻辑) + if ($insertData) { + Db::table('fb_leads')->insert($insertData); + } + } + +} \ No newline at end of file diff --git a/app/inquery/utils/FbLeadHeaderMapper.php b/app/inquery/utils/FbLeadHeaderMapper.php new file mode 100644 index 0000000..59b1cb5 --- /dev/null +++ b/app/inquery/utils/FbLeadHeaderMapper.php @@ -0,0 +1,154 @@ + DB 字段名 + * 注意:key 用的是“原始表头字符串”(包括下划线) + */ + protected array $headerMap = [ + // ===== 基础公共字段 ===== + 'id' => 'fb_lead_id', + 'created_time' => 'fb_created_time', + 'ad_id' => 'ad_id', + 'ad_name' => 'ad_name', + 'adset_id' => 'adset_id', + 'adset_name' => 'adset_name', + 'campaign_id' => 'campaign_id', + 'campaign_name' => 'campaign_name', + 'form_id' => 'form_id', + 'form_name' => 'form_name', + 'is_organic' => 'is_organic', + 'platform' => 'platform', + + 'full_name' => 'full_name', + 'phone' => 'phone', + 'phone_number' => 'phone', // 兼容写法 + 'email' => 'email', + 'lead_status' => 'lead_status', + 'inbox_url' => 'inbox_url', + + // ===== 车型相关:全部归到 car_model ===== + 'which_model_of_this_car_would_you_like_to_purchase?_' => 'car_model', + 'which_model_of_this_car_would_you_like_to_purchase?' => 'car_model', + 'which_car_model_would_you_like_to_purchase?' => 'car_model', + 'which_model_do_you_want_to_purchase?' => 'car_model', + 'which_model_would_you_like_to_purchase?' => 'car_model', + 'which_version_do_you_want_to_buy?' => 'car_model', // 这里也归到车型/版本字段 + + // ===== 数量相关:全部归到 car_quantity ===== + 'how_many_cars_would_you_like_to_buy?_' => 'car_quantity', + 'how_many_cars_would_you_like_to_buy?' => 'car_quantity', + '_how_many_cars_would_you_like_to_buy?' => 'car_quantity', + 'how_many_cars_do_you_buy?' => 'car_quantity', + 'how_many_cars_do_you_want_to_purchase?' => 'car_quantity', + 'how_many_cars_do_you_want_to_buy?' => 'car_quantity', + 'what_quantity_do_you_want_to_buy?' => 'car_quantity', + + // ===== 颜色相关:归到 favorite_color ===== + 'what_color_do_you_like?' => 'favorite_color', + 'which_color_would_you_like_to_purchase?' => 'favorite_color', + + // ===== 用途相关:归到 usage_type ===== + 'are_you_using_it_for_business_or_personal_use?' => 'usage_type', + 'business_use_or_personal_use?' => 'usage_type', + 'personal_use_or_business_use?' => 'usage_type', + + // ===== 座位数相关 ===== + 'do_you_want_to_buy_six_seats_or_seven_seats?' => 'seat_preference', + ]; + + /** + * 返回所有支持的 Excel 表头 + */ + public function getSupportedHeaders(): array + { + return array_keys($this->headerMap); + } + + /** + * 单个表头映射:原始表头 -> DB 字段名 + */ + public function mapHeader(string $rawHeader): ?string + { + $key = $this->normalizeHeader($rawHeader); + + return $this->headerMap[$key] ?? null; + } + + /** + * 校验一整行表头: + * - 有任何不认识的表头,就抛 BusinessException + */ + public function validateHeaders(array $rawHeaders): void + { + $unsupported = []; + + foreach ($rawHeaders as $header) { + $header = (string)$header; + $key = $this->normalizeHeader($header); + + // 允许空表头(多余空列) + if ($key === '') { + continue; + } + + if (!isset($this->headerMap[$key])) { + $unsupported[] = $header; + } + } + + if (!empty($unsupported)) { + $msg = 'Excel 表头包含未兼容字段:' . implode('、', $unsupported) + . '。请检查这些字段是否需要加入映射配置。'; + throw new BusinessException($msg); + } + } + + /** + * 根据第一行表头,构建 “列索引 => DB 字段” 映射 + */ + public function buildColumnFieldMap(array $rawHeaders): array + { + // 先做一遍校验(发现问题直接抛异常) + $this->validateHeaders($rawHeaders); + + $colToField = []; + + foreach ($rawHeaders as $index => $header) { + $key = $this->normalizeHeader((string)$header); + if ($key === '') { + continue; + } + + $field = $this->headerMap[$key] ?? null; + if ($field === null) { + continue; // 理论上不会到这儿,双保险 + } + + $colToField[$index] = $field; + } + + return $colToField; + } + + /** + * 表头标准化: + * 目前只去 BOM + trim,保持和 headerMap 中 key 完全一致 + * 如果后面再出现“有空格/大小写”的版本,我们可以在这里做 strtolower / 替换空格 为 _ + */ + protected function normalizeHeader(string $header): string + { + // 去掉 UTF-8 BOM + $header = preg_replace('/^\xEF\xBB\xBF/', '', $header); + + // 去掉前后空白 + $header = trim($header); + + return $header; + } +} diff --git a/app/inquery/utils/PhoneCountryHelper.php b/app/inquery/utils/PhoneCountryHelper.php new file mode 100644 index 0000000..78ec4ab --- /dev/null +++ b/app/inquery/utils/PhoneCountryHelper.php @@ -0,0 +1,58 @@ + + + if (str_starts_with($phone, '00')) { + $phone = '+' . substr($phone, 2); + } elseif ($phone[0] !== '+') { + // 没有 +,默认补一个(大多数线索都是国际号) + $phone = '+' . $phone; + } + + $digits = ltrim($phone, '+'); + if ($digits === '') { + return null; + } + + // 3. 国家区号最长 3 位,从长到短匹配 + for ($len = 3; $len >= 1; $len--) { + $code = substr($digits, 0, $len); + if ($code === '') { + continue; + } + + $country = Db::table('country') + ->where('phone_code', $code) + ->first(); + + if ($country) { + return (int)$country->id; + } + } + + // 没找到 + return null; + } +} diff --git a/app/inquery/utils/RoundRobinAssigner.php b/app/inquery/utils/RoundRobinAssigner.php new file mode 100644 index 0000000..5063884 --- /dev/null +++ b/app/inquery/utils/RoundRobinAssigner.php @@ -0,0 +1,46 @@ + $v > 0)); + if (empty($userIds)) { + throw new BusinessException('轮询分配业务员失败:业务员列表不能为空'); + } + + $count = count($userIds); + + // Redis key:同一个 groupKey 共用一条轮询链 + $key = self::REDIS_KEY_PREFIX . $groupKey; + + // 使用 INCR 做自增计数,天然是原子操作,适合并发环境 + // 第一次调用 INCR 返回 1,我们用 (value - 1) 来做索引,从 0 开始 + $val = Redis::incr($key); // 如果不存在,Redis 会先置为 0 再加 1 + + $index = ($val - 1) % $count; + + return $userIds[$index]; + } +} diff --git a/app/utils/Cf7InquiryClient.php b/app/utils/Cf7InquiryClient.php new file mode 100644 index 0000000..adf264b --- /dev/null +++ b/app/utils/Cf7InquiryClient.php @@ -0,0 +1,93 @@ +baseUri = rtrim($baseUri, '/'); + + $config = [ + 'base_uri' => $this->baseUri, + 'auth' => [$username, $password], + 'timeout' => $options['timeout'] ?? 10, + 'verify' => $options['verify'] ?? false, // 正式环境建议改成 true + ]; + + $this->client = new Client($config); + } + + /** + * 获取当前站点的 CF7 表单列表(从 Advanced CF7 DB 推断) + * 对应插件里的 /acf7db/v1/forms + * + * @return array + * @throws GuzzleException + */ + public function fetchForms(): array + { + $response = $this->client->get('/wp-json/acf7db/v1/forms'); + $body = (string)$response->getBody(); + + return json_decode($body, true) ?: []; + } + + /** + * 获取 Advanced CF7 DB 询盘列表(存量 + 增量通用) + * + * @param int $cf7Id 表单 ID(可选,为 0 时表示所有表单) + * @param int $lastId 上次同步到的最大 submit_id + * @param int $perPage 每次拉取数量 + * @param string|null $from 起始时间(可选,Y-m-d H:i:s) + * @param string|null $to 结束时间(可选) + * + * @return array ['data' => [...], 'meta' => [...]] + * @throws GuzzleException + */ + public function fetchEntries( + int $cf7Id = 0, + int $lastId = 0, + int $perPage = 200, + ?string $from = null, + ?string $to = null + ): array { + $query = [ + 'last_id' => $lastId, + 'per_page' => $perPage, + ]; + + if ($cf7Id > 0) { + $query['cf7_id'] = $cf7Id; + } + if ($from) { + $query['from'] = $from; + } + if ($to) { + $query['to'] = $to; + } + + $response = $this->client->get('/wp-json/acf7db/v1/entries', [ + 'query' => $query, + ]); + + $body = (string)$response->getBody(); + return json_decode($body, true) ?: ['data' => [], 'meta' => []]; + } +} diff --git a/composer.json b/composer.json index 34464e3..b51dd8c 100644 --- a/composer.json +++ b/composer.json @@ -29,11 +29,15 @@ "monolog/monolog": "^2.0", "vlucas/phpdotenv": "^5.6", "webman/database": "^2.1", - "illuminate/pagination": "^12.37", - "illuminate/events": "^12.37", - "symfony/var-dumper": "^7.3", + "illuminate/pagination": "^12.42", + "illuminate/events": "^12.42", + "symfony/var-dumper": "^7.4", "phpmailer/phpmailer": "^7.0", - "zoujingli/ip2region": "^3.0" + "zoujingli/ip2region": "^3.0", + "phpoffice/phpspreadsheet": "^5.3", + "webman/console": "^2.1", + "webman/redis": "^2.1", + "webman/redis-queue": "^2.1" }, "suggest": { "ext-event": "For better performance. " diff --git a/composer.lock b/composer.lock index d54d545..4d5f7c3 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "48c77686c3f22f2ca6b7174d010a7656", + "content-hash": "bacff953706f64356bf4c93c5e0f7dad", "packages": [ { "name": "brick/math", @@ -135,6 +135,85 @@ ], "time": "2024-02-09T16:56:22+00:00" }, + { + "name": "composer/pcre", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" + }, + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-11-12T16:29:46+00:00" + }, { "name": "doctrine/inflector", "version": "2.1.0", @@ -771,7 +850,7 @@ }, { "name": "illuminate/bus", - "version": "v12.41.1", + "version": "v12.42.0", "source": { "type": "git", "url": "https://github.com/illuminate/bus.git", @@ -824,16 +903,16 @@ }, { "name": "illuminate/collections", - "version": "v12.41.1", + "version": "v12.42.0", "source": { "type": "git", "url": "https://github.com/illuminate/collections.git", - "reference": "1cf6115f711ff775fcce547dee82d59cac33fedb" + "reference": "16657effa6a5a4e728f9aeb3e38fb4fa9ba70e7d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/collections/zipball/1cf6115f711ff775fcce547dee82d59cac33fedb", - "reference": "1cf6115f711ff775fcce547dee82d59cac33fedb", + "url": "https://api.github.com/repos/illuminate/collections/zipball/16657effa6a5a4e728f9aeb3e38fb4fa9ba70e7d", + "reference": "16657effa6a5a4e728f9aeb3e38fb4fa9ba70e7d", "shasum": "" }, "require": { @@ -879,11 +958,11 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-11-29T13:55:43+00:00" + "time": "2025-12-06T18:08:25+00:00" }, { "name": "illuminate/conditionable", - "version": "v12.41.1", + "version": "v12.42.0", "source": { "type": "git", "url": "https://github.com/illuminate/conditionable.git", @@ -929,20 +1008,21 @@ }, { "name": "illuminate/container", - "version": "v12.41.1", + "version": "v12.42.0", "source": { "type": "git", "url": "https://github.com/illuminate/container.git", - "reference": "0489bc396de1537537750babec3a03ee7b753d1f" + "reference": "326667a4c813e3ad5a645969a7e3f5c10d159de2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/container/zipball/0489bc396de1537537750babec3a03ee7b753d1f", - "reference": "0489bc396de1537537750babec3a03ee7b753d1f", + "url": "https://api.github.com/repos/illuminate/container/zipball/326667a4c813e3ad5a645969a7e3f5c10d159de2", + "reference": "326667a4c813e3ad5a645969a7e3f5c10d159de2", "shasum": "" }, "require": { "illuminate/contracts": "^12.0", + "illuminate/reflection": "^12.0", "php": "^8.2", "psr/container": "^1.1.1|^2.0.1", "symfony/polyfill-php84": "^1.33", @@ -986,11 +1066,11 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-11-28T18:51:53+00:00" + "time": "2025-12-08T22:34:25+00:00" }, { "name": "illuminate/contracts", - "version": "v12.41.1", + "version": "v12.42.0", "source": { "type": "git", "url": "https://github.com/illuminate/contracts.git", @@ -1038,16 +1118,16 @@ }, { "name": "illuminate/database", - "version": "v12.41.1", + "version": "v12.42.0", "source": { "type": "git", "url": "https://github.com/illuminate/database.git", - "reference": "199e9200a08c1841a31932e0a36a4094de84b72d" + "reference": "b44f021fee7b48289312e22fd6e4d580374a86ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/database/zipball/199e9200a08c1841a31932e0a36a4094de84b72d", - "reference": "199e9200a08c1841a31932e0a36a4094de84b72d", + "url": "https://api.github.com/repos/illuminate/database/zipball/b44f021fee7b48289312e22fd6e4d580374a86ac", + "reference": "b44f021fee7b48289312e22fd6e4d580374a86ac", "shasum": "" }, "require": { @@ -1105,11 +1185,11 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-12-01T15:01:30+00:00" + "time": "2025-12-06T18:08:25+00:00" }, { "name": "illuminate/events", - "version": "v12.41.1", + "version": "v12.42.0", "source": { "type": "git", "url": "https://github.com/illuminate/events.git", @@ -1164,7 +1244,7 @@ }, { "name": "illuminate/filesystem", - "version": "v12.41.1", + "version": "v12.42.0", "source": { "type": "git", "url": "https://github.com/illuminate/filesystem.git", @@ -1231,16 +1311,16 @@ }, { "name": "illuminate/http", - "version": "v12.41.1", + "version": "v12.42.0", "source": { "type": "git", "url": "https://github.com/illuminate/http.git", - "reference": "cdabd462fa50ab057da4807b980c861326c5a345" + "reference": "b7997f2faffb3c31f2a0fa079428ff6bfcce8907" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/http/zipball/cdabd462fa50ab057da4807b980c861326c5a345", - "reference": "cdabd462fa50ab057da4807b980c861326c5a345", + "url": "https://api.github.com/repos/illuminate/http/zipball/b7997f2faffb3c31f2a0fa079428ff6bfcce8907", + "reference": "b7997f2faffb3c31f2a0fa079428ff6bfcce8907", "shasum": "" }, "require": { @@ -1289,11 +1369,11 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-11-28T18:00:52+00:00" + "time": "2025-12-09T15:25:50+00:00" }, { "name": "illuminate/macroable", - "version": "v12.41.1", + "version": "v12.42.0", "source": { "type": "git", "url": "https://github.com/illuminate/macroable.git", @@ -1339,7 +1419,7 @@ }, { "name": "illuminate/pagination", - "version": "v12.41.1", + "version": "v12.42.0", "source": { "type": "git", "url": "https://github.com/illuminate/pagination.git", @@ -1389,7 +1469,7 @@ }, { "name": "illuminate/pipeline", - "version": "v12.41.1", + "version": "v12.42.0", "source": { "type": "git", "url": "https://github.com/illuminate/pipeline.git", @@ -1439,9 +1519,114 @@ }, "time": "2025-08-20T13:36:50+00:00" }, + { + "name": "illuminate/redis", + "version": "v12.42.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/redis.git", + "reference": "8dc6682638100db9feceaac3db206de752dd0f9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/redis/zipball/8dc6682638100db9feceaac3db206de752dd0f9a", + "reference": "8dc6682638100db9feceaac3db206de752dd0f9a", + "shasum": "" + }, + "require": { + "illuminate/collections": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/macroable": "^12.0", + "illuminate/support": "^12.0", + "php": "^8.2" + }, + "suggest": { + "ext-redis": "Required to use the phpredis connector (^4.0|^5.0|^6.0).", + "predis/predis": "Required to use the predis connector (^2.3|^3.0)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "12.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Redis\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Redis package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-11-20T20:50:40+00:00" + }, + { + "name": "illuminate/reflection", + "version": "v12.42.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/reflection.git", + "reference": "7b86bc570d5b75e4a3ad79f8cca1491ba24b7c75" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/reflection/zipball/7b86bc570d5b75e4a3ad79f8cca1491ba24b7c75", + "reference": "7b86bc570d5b75e4a3ad79f8cca1491ba24b7c75", + "shasum": "" + }, + "require": { + "illuminate/collections": "^12.0", + "illuminate/contracts": "^12.0", + "php": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "12.x-dev" + } + }, + "autoload": { + "files": [ + "helpers.php" + ], + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Reflection package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-12-09T15:11:22+00:00" + }, { "name": "illuminate/session", - "version": "v12.41.1", + "version": "v12.42.0", "source": { "type": "git", "url": "https://github.com/illuminate/session.git", @@ -1498,16 +1683,16 @@ }, { "name": "illuminate/support", - "version": "v12.41.1", + "version": "v12.42.0", "source": { "type": "git", "url": "https://github.com/illuminate/support.git", - "reference": "c65715d8a41863ee3f2e22dc796b75c62143fca2" + "reference": "d35411be5657e0b5560a5885f3c9140e4cbe0be5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/illuminate/support/zipball/c65715d8a41863ee3f2e22dc796b75c62143fca2", - "reference": "c65715d8a41863ee3f2e22dc796b75c62143fca2", + "url": "https://api.github.com/repos/illuminate/support/zipball/d35411be5657e0b5560a5885f3c9140e4cbe0be5", + "reference": "d35411be5657e0b5560a5885f3c9140e4cbe0be5", "shasum": "" }, "require": { @@ -1519,6 +1704,7 @@ "illuminate/conditionable": "^12.0", "illuminate/contracts": "^12.0", "illuminate/macroable": "^12.0", + "illuminate/reflection": "^12.0", "nesbot/carbon": "^3.8.4", "php": "^8.2", "symfony/polyfill-php83": "^1.33", @@ -1573,7 +1759,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-12-03T01:01:36+00:00" + "time": "2025-12-09T15:26:52+00:00" }, { "name": "laravel/serializable-closure", @@ -1636,6 +1822,191 @@ }, "time": "2025-11-21T20:52:36+00:00" }, + { + "name": "maennchen/zipstream-php", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/maennchen/ZipStream-PHP.git", + "reference": "9712d8fa4cdf9240380b01eb4be55ad8dcf71416" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/9712d8fa4cdf9240380b01eb4be55ad8dcf71416", + "reference": "9712d8fa4cdf9240380b01eb4be55ad8dcf71416", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "ext-zlib": "*", + "php-64bit": "^8.3" + }, + "require-dev": { + "brianium/paratest": "^7.7", + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.16", + "guzzlehttp/guzzle": "^7.5", + "mikey179/vfsstream": "^1.6", + "php-coveralls/php-coveralls": "^2.5", + "phpunit/phpunit": "^12.0", + "vimeo/psalm": "^6.0" + }, + "suggest": { + "guzzlehttp/psr7": "^2.4", + "psr/http-message": "^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "ZipStream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paul Duncan", + "email": "pabs@pablotron.org" + }, + { + "name": "Jonatan Männchen", + "email": "jonatan@maennchen.ch" + }, + { + "name": "Jesse Donat", + "email": "donatj@gmail.com" + }, + { + "name": "András Kolesár", + "email": "kolesar@kolesar.hu" + } + ], + "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.", + "keywords": [ + "stream", + "zip" + ], + "support": { + "issues": "https://github.com/maennchen/ZipStream-PHP/issues", + "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.2.0" + }, + "funding": [ + { + "url": "https://github.com/maennchen", + "type": "github" + } + ], + "time": "2025-07-17T11:15:13+00:00" + }, + { + "name": "markbaker/complex", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPComplex.git", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Complex\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@lange.demon.co.uk" + } + ], + "description": "PHP Class for working with complex numbers", + "homepage": "https://github.com/MarkBaker/PHPComplex", + "keywords": [ + "complex", + "mathematics" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPComplex/issues", + "source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.2" + }, + "time": "2022-12-06T16:21:08+00:00" + }, + { + "name": "markbaker/matrix", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPMatrix.git", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/728434227fe21be27ff6d86621a1b13107a2562c", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpdocumentor/phpdocumentor": "2.*", + "phploc/phploc": "^4.0", + "phpmd/phpmd": "2.*", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "sebastian/phpcpd": "^4.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Matrix\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@demon-angel.eu" + } + ], + "description": "PHP Class for working with matrices", + "homepage": "https://github.com/MarkBaker/PHPMatrix", + "keywords": [ + "mathematics", + "matrix", + "vector" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPMatrix/issues", + "source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.1" + }, + "time": "2022-12-02T22:17:43+00:00" + }, { "name": "monolog/monolog", "version": "2.10.0", @@ -1975,6 +2346,112 @@ ], "time": "2025-11-25T07:18:09+00:00" }, + { + "name": "phpoffice/phpspreadsheet", + "version": "5.3.0", + "source": { + "type": "git", + "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", + "reference": "4d597c1aacdde1805a33c525b9758113ea0d90df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/4d597c1aacdde1805a33c525b9758113ea0d90df", + "reference": "4d597c1aacdde1805a33c525b9758113ea0d90df", + "shasum": "" + }, + "require": { + "composer/pcre": "^1||^2||^3", + "ext-ctype": "*", + "ext-dom": "*", + "ext-fileinfo": "*", + "ext-gd": "*", + "ext-iconv": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-xml": "*", + "ext-xmlreader": "*", + "ext-xmlwriter": "*", + "ext-zip": "*", + "ext-zlib": "*", + "maennchen/zipstream-php": "^2.1 || ^3.0", + "markbaker/complex": "^3.0", + "markbaker/matrix": "^3.0", + "php": "^8.1", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-main", + "dompdf/dompdf": "^2.0 || ^3.0", + "friendsofphp/php-cs-fixer": "^3.2", + "mitoteam/jpgraph": "^10.5", + "mpdf/mpdf": "^8.1.1", + "phpcompatibility/php-compatibility": "^9.3", + "phpstan/phpstan": "^1.1 || ^2.0", + "phpstan/phpstan-deprecation-rules": "^1.0 || ^2.0", + "phpstan/phpstan-phpunit": "^1.0 || ^2.0", + "phpunit/phpunit": "^10.5", + "squizlabs/php_codesniffer": "^3.7", + "tecnickcom/tcpdf": "^6.5" + }, + "suggest": { + "dompdf/dompdf": "Option for rendering PDF with PDF Writer", + "ext-intl": "PHP Internationalization Functions, required for NumberFormat Wizard", + "mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers", + "mpdf/mpdf": "Option for rendering PDF with PDF Writer", + "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Maarten Balliauw", + "homepage": "https://blog.maartenballiauw.be" + }, + { + "name": "Mark Baker", + "homepage": "https://markbakeruk.net" + }, + { + "name": "Franck Lefevre", + "homepage": "https://rootslabs.net" + }, + { + "name": "Erik Tilt" + }, + { + "name": "Adrien Crivelli" + } + ], + "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", + "homepage": "https://github.com/PHPOffice/PhpSpreadsheet", + "keywords": [ + "OpenXML", + "excel", + "gnumeric", + "ods", + "php", + "spreadsheet", + "xls", + "xlsx" + ], + "support": { + "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues", + "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/5.3.0" + }, + "time": "2025-11-24T15:47:10+00:00" + }, { "name": "phpoption/phpoption", "version": "1.9.4", @@ -2584,6 +3061,104 @@ ], "time": "2025-11-12T15:39:26+00:00" }, + { + "name": "symfony/console", + "version": "v7.4.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "6d9f0fbf2ec2e9785880096e3abd0ca0c88b506e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/6d9f0fbf2ec2e9785880096e3abd0ca0c88b506e", + "reference": "6d9f0fbf2ec2e9785880096e3abd0ca0c88b506e", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.2|^8.0" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/lock": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v7.4.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-12-05T15:23:39+00:00" + }, { "name": "symfony/deprecation-contracts", "version": "v3.6.0", @@ -3335,6 +3910,88 @@ ], "time": "2024-09-09T11:45:10+00:00" }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-27T09:58:17+00:00" + }, { "name": "symfony/polyfill-intl-idn", "version": "v1.33.0", @@ -3916,6 +4573,184 @@ ], "time": "2025-06-23T16:12:55+00:00" }, + { + "name": "symfony/service-contracts", + "version": "v3.6.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-15T11:30:57+00:00" + }, + { + "name": "symfony/string", + "version": "v7.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "d50e862cb0a0e0886f73ca1f31b865efbb795003" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/d50e862cb0a0e0886f73ca1f31b865efbb795003", + "reference": "d50e862cb0a0e0886f73ca1f31b865efbb795003", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.33", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.1|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v7.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-11-27T13:27:24+00:00" + }, { "name": "symfony/translation", "version": "v7.4.0", @@ -4343,6 +5178,60 @@ ], "time": "2024-11-21T01:49:47+00:00" }, + { + "name": "webman/console", + "version": "v2.1.10", + "source": { + "type": "git", + "url": "https://github.com/webman-php/console.git", + "reference": "6b24bb77d04588c7c3b235087073e69ddfdaa773" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webman-php/console/zipball/6b24bb77d04588c7c3b235087073e69ddfdaa773", + "reference": "6b24bb77d04588c7c3b235087073e69ddfdaa773", + "shasum": "" + }, + "require": { + "doctrine/inflector": "^2.0", + "php": ">=8.1", + "symfony/console": "^6.0 || ^7.0" + }, + "require-dev": { + "workerman/webman": "^2.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Webman\\Console\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "walkor", + "email": "walkor@workerman.net", + "homepage": "http://www.workerman.net", + "role": "Developer" + } + ], + "description": "Webman console", + "homepage": "http://www.workerman.net", + "keywords": [ + "webman console" + ], + "support": { + "email": "walkor@workerman.net", + "forum": "http://www.workerman.net/questions", + "issues": "https://github.com/webman-php/console/issues", + "source": "https://github.com/webman-php/console", + "wiki": "http://www.workerman.net/doc/webman" + }, + "time": "2025-12-08T08:23:12+00:00" + }, { "name": "webman/database", "version": "v2.1.7", @@ -4381,6 +5270,76 @@ }, "time": "2025-11-27T08:09:25+00:00" }, + { + "name": "webman/redis", + "version": "v2.1.3", + "source": { + "type": "git", + "url": "https://github.com/webman-php/redis.git", + "reference": "559eb1692d39c6fef5cf526223fff728be6c0fb9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webman-php/redis/zipball/559eb1692d39c6fef5cf526223fff728be6c0fb9", + "reference": "559eb1692d39c6fef5cf526223fff728be6c0fb9", + "shasum": "" + }, + "require": { + "illuminate/redis": "^10.0 || ^11.0 || ^12.0", + "workerman/webman-framework": "^2.1 || dev-master" + }, + "type": "library", + "autoload": { + "psr-4": { + "support\\": "src/support", + "Webman\\Redis\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Webman redis", + "support": { + "issues": "https://github.com/webman-php/redis/issues", + "source": "https://github.com/webman-php/redis/tree/v2.1.3" + }, + "time": "2025-03-14T03:52:14+00:00" + }, + { + "name": "webman/redis-queue", + "version": "v2.1.1", + "source": { + "type": "git", + "url": "https://github.com/webman-php/redis-queue.git", + "reference": "ff4791e21f3c324a47e21da7b6f2dae5a7311dcb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webman-php/redis-queue/zipball/ff4791e21f3c324a47e21da7b6f2dae5a7311dcb", + "reference": "ff4791e21f3c324a47e21da7b6f2dae5a7311dcb", + "shasum": "" + }, + "require": { + "ext-redis": "*", + "php": ">=8.1", + "workerman/redis-queue": "^1.2", + "workerman/webman-framework": "^2.1 || dev-master" + }, + "type": "library", + "autoload": { + "psr-4": { + "Webman\\RedisQueue\\": "./src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "description": "Redis message queue plugin for webman.", + "support": { + "issues": "https://github.com/webman-php/redis-queue/issues", + "source": "https://github.com/webman-php/redis-queue/tree/v2.1.1" + }, + "time": "2025-11-14T07:12:52+00:00" + }, { "name": "workerman/coroutine", "version": "v1.1.4", @@ -4420,6 +5379,78 @@ }, "time": "2025-10-11T15:09:08+00:00" }, + { + "name": "workerman/redis", + "version": "v2.0.5", + "source": { + "type": "git", + "url": "https://github.com/walkor/redis.git", + "reference": "49627c1809eff1ef7175eb8ee7549234a1d67ec5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/redis/zipball/49627c1809eff1ef7175eb8ee7549234a1d67ec5", + "reference": "49627c1809eff1ef7175eb8ee7549234a1d67ec5", + "shasum": "" + }, + "require": { + "php": ">=7", + "workerman/workerman": "^4.1.0||^5.0.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Workerman\\Redis\\": "./src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "homepage": "http://www.workerman.net", + "support": { + "issues": "https://github.com/walkor/redis/issues", + "source": "https://github.com/walkor/redis/tree/v2.0.5" + }, + "time": "2025-04-07T01:58:58+00:00" + }, + { + "name": "workerman/redis-queue", + "version": "v1.2.1", + "source": { + "type": "git", + "url": "https://github.com/walkor/redis-queue.git", + "reference": "75dbf7ed2ea228c45dc0df82c0fea35879b715d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/redis-queue/zipball/75dbf7ed2ea228c45dc0df82c0fea35879b715d0", + "reference": "75dbf7ed2ea228c45dc0df82c0fea35879b715d0", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "workerman/redis": "^1.0||^2.0", + "workerman/workerman": ">=4.0.20" + }, + "type": "library", + "autoload": { + "psr-4": { + "Workerman\\RedisQueue\\": "./src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Message queue system written in PHP based on workerman and backed by Redis.", + "homepage": "http://www.workerman.net", + "support": { + "issues": "https://github.com/walkor/redis-queue/issues", + "source": "https://github.com/walkor/redis-queue/tree/v1.2.1" + }, + "time": "2025-01-02T09:21:45+00:00" + }, { "name": "workerman/webman-framework", "version": "v2.1.4", diff --git a/config/database.php b/config/database.php index e436e95..93308ad 100644 --- a/config/database.php +++ b/config/database.php @@ -4,14 +4,14 @@ return [ 'connections' => [ 'mysql' => [ 'driver' => 'mysql', - 'host' => '127.0.0.1', - 'port' => '3306', - 'database' => 'your_database', - 'username' => 'your_username', - 'password' => 'your_password', + 'host' => getenv('DB_HOST'), + 'port' => getenv('DB_PORT'), + 'database' => getenv('DB_NAME'), + 'username' => getenv('DB_USER'), + 'password' => getenv('DB_PASSWORD'), 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_general_ci', - 'prefix' => '', + 'prefix' => getenv('DB_PREFIX'), 'strict' => true, 'engine' => null, 'options' => [ diff --git a/config/plugin/webman/console/app.php b/config/plugin/webman/console/app.php new file mode 100644 index 0000000..074e986 --- /dev/null +++ b/config/plugin/webman/console/app.php @@ -0,0 +1,24 @@ + true, + + 'build_dir' => BASE_PATH . DIRECTORY_SEPARATOR . 'build', + + 'phar_filename' => 'webman.phar', + + 'bin_filename' => 'webman.bin', + + 'signature_algorithm'=> Phar::SHA256, //set the signature algorithm for a phar and apply it. The signature algorithm must be one of Phar::MD5, Phar::SHA1, Phar::SHA256, Phar::SHA512, or Phar::OPENSSL. + + 'private_key_file' => '', // The file path for certificate or OpenSSL private key file. + + 'exclude_pattern' => '#^(?!.*(composer.json|/.github/|/.idea/|/.git/|/.setting/|/runtime/|/vendor-bin/|/build/|/vendor/webman/admin/))(.*)$#', + + 'exclude_files' => [ + '.env', 'LICENSE', 'composer.json', 'composer.lock', 'start.php', 'webman.phar', 'webman.bin' + ], + + 'custom_ini' => ' +memory_limit = 256M + ', +]; diff --git a/config/plugin/webman/redis-queue/app.php b/config/plugin/webman/redis-queue/app.php new file mode 100644 index 0000000..8f9c426 --- /dev/null +++ b/config/plugin/webman/redis-queue/app.php @@ -0,0 +1,4 @@ + true, +]; \ No newline at end of file diff --git a/config/plugin/webman/redis-queue/command.php b/config/plugin/webman/redis-queue/command.php new file mode 100644 index 0000000..8bfe2a1 --- /dev/null +++ b/config/plugin/webman/redis-queue/command.php @@ -0,0 +1,7 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + 'default' => [ + 'handlers' => [ + [ + 'class' => Monolog\Handler\RotatingFileHandler::class, + 'constructor' => [ + runtime_path() . '/logs/redis-queue/queue.log', + 7, //$maxFiles + Monolog\Logger::DEBUG, + ], + 'formatter' => [ + 'class' => Monolog\Formatter\LineFormatter::class, + 'constructor' => [null, 'Y-m-d H:i:s', true], + ], + ] + ], + ] +]; diff --git a/config/plugin/webman/redis-queue/process.php b/config/plugin/webman/redis-queue/process.php new file mode 100644 index 0000000..c8d4da1 --- /dev/null +++ b/config/plugin/webman/redis-queue/process.php @@ -0,0 +1,11 @@ + [ + 'handler' => Webman\RedisQueue\Process\Consumer::class, + 'count' => 8, // 可以设置多进程同时消费 + 'constructor' => [ + // 消费者类目录 + 'consumer_dir' => app_path() . '/queue/redis' + ] + ] +]; \ No newline at end of file diff --git a/config/plugin/webman/redis-queue/redis.php b/config/plugin/webman/redis-queue/redis.php new file mode 100644 index 0000000..420914d --- /dev/null +++ b/config/plugin/webman/redis-queue/redis.php @@ -0,0 +1,22 @@ + [ +// 'host' => 'redis://127.0.0.1:6379', + 'host' => getenv('REDIS_QUEUE_HOST'), + 'options' => [ + 'auth' => getenv('REDIS_QUEUE_AUTH'), // 密码,字符串类型,可选参数 + 'db' => getenv('REDIS_QUEUE_DB'), // 数据库 + 'prefix' => getenv('REDIS_QUEUE_PREFIX'), // key 前缀 + 'max_attempts' => getenv('REDIS_QUEUE_MAX_ATTEMPTS'), // 消费失败后,重试次数 + 'retry_seconds' => getenv('REDIS_QUEUE_RETRY_SECONDS'), // 重试间隔,单位秒 + ], + // Connection pool, supports only Swoole or Swow drivers. + 'pool' => [ + 'max_connections' => 5, + 'min_connections' => 1, + 'wait_timeout' => 3, + 'idle_timeout' => 60, + 'heartbeat_interval' => 50, + ] + ], +]; diff --git a/config/redis.php b/config/redis.php new file mode 100644 index 0000000..0508ef6 --- /dev/null +++ b/config/redis.php @@ -0,0 +1,29 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + 'default' => [ + 'password' => getenv('REDIS_PASSWORD'), + 'host' => getenv('REDIS_HOST'), + 'port' => getenv('REDIS_PORT'), + 'database' => getenv('REDIS_DATABASE'), + 'pool' => [ + 'max_connections' => 5, + 'min_connections' => 1, + 'wait_timeout' => 3, + 'idle_timeout' => 60, + 'heartbeat_interval' => 50, + ], + ] +]; diff --git a/config/route.php b/config/route.php index cb45b8d..fd6c729 100644 --- a/config/route.php +++ b/config/route.php @@ -16,6 +16,6 @@ use Webman\Route; -Route::any('/form/{id}', [app\controller\IndexController::class, 'form']); - +Route::any('/ts/form', [app\controller\TransmitController::class, 'form']); +Route::any('/iq/form', [app\inquery\controller\InqueryController::class, 'form']); diff --git a/public/excel/BYD tang_Leads_2025-12-04_2025-12-09.xlsx b/public/excel/BYD tang_Leads_2025-12-04_2025-12-09.xlsx new file mode 100644 index 0000000..9c6b3b5 Binary files /dev/null and b/public/excel/BYD tang_Leads_2025-12-04_2025-12-09.xlsx differ diff --git a/public/excel/Corolla Cross_Leads_2025-12-04_2025-12-09.xlsx b/public/excel/Corolla Cross_Leads_2025-12-04_2025-12-09.xlsx new file mode 100644 index 0000000..313ce50 Binary files /dev/null and b/public/excel/Corolla Cross_Leads_2025-12-04_2025-12-09.xlsx differ diff --git a/public/excel/ROX 01_Leads_2025-12-04_2025-12-09.xlsx b/public/excel/ROX 01_Leads_2025-12-04_2025-12-09.xlsx new file mode 100644 index 0000000..bae4f24 Binary files /dev/null and b/public/excel/ROX 01_Leads_2025-12-04_2025-12-09.xlsx differ diff --git a/public/excel/Toyota RAV4_Leads_2025-12-04_2025-12-09.xlsx b/public/excel/Toyota RAV4_Leads_2025-12-04_2025-12-09.xlsx new file mode 100644 index 0000000..c5c718e Binary files /dev/null and b/public/excel/Toyota RAV4_Leads_2025-12-04_2025-12-09.xlsx differ diff --git a/public/excel/ZHENGZHOU NISSAN Z9_Leads_2025-12-04_2025-12-09.xlsx b/public/excel/ZHENGZHOU NISSAN Z9_Leads_2025-12-04_2025-12-09.xlsx new file mode 100644 index 0000000..751faa2 Binary files /dev/null and b/public/excel/ZHENGZHOU NISSAN Z9_Leads_2025-12-04_2025-12-09.xlsx differ diff --git a/public/excel/byd song plus_Leads_2025-12-04_2025-12-09.xlsx b/public/excel/byd song plus_Leads_2025-12-04_2025-12-09.xlsx new file mode 100644 index 0000000..3c5d456 Binary files /dev/null and b/public/excel/byd song plus_Leads_2025-12-04_2025-12-09.xlsx differ diff --git a/public/excel/jetour t2_Leads_2025-12-04_2025-12-09.xlsx b/public/excel/jetour t2_Leads_2025-12-04_2025-12-09.xlsx new file mode 100644 index 0000000..ef512a1 Binary files /dev/null and b/public/excel/jetour t2_Leads_2025-12-04_2025-12-09.xlsx differ diff --git a/public/excel/leopard 5_Leads_2025-12-04_2025-12-09.xlsx b/public/excel/leopard 5_Leads_2025-12-04_2025-12-09.xlsx new file mode 100644 index 0000000..d31c5e9 Binary files /dev/null and b/public/excel/leopard 5_Leads_2025-12-04_2025-12-09.xlsx differ diff --git a/webman b/webman new file mode 100755 index 0000000..3f55062 --- /dev/null +++ b/webman @@ -0,0 +1,71 @@ +#!/usr/bin/env php +load(); + } else { + Dotenv::createMutable(run_path())->load(); + } +} + +$appConfig = require $appConfigFile; +if ($timezone = $appConfig['default_timezone'] ?? '') { + date_default_timezone_set($timezone); +} + +if ($errorReporting = $appConfig['error_reporting'] ?? '') { + error_reporting($errorReporting); +} + +if (!in_array($argv[1] ?? '', ['start', 'restart', 'stop', 'status', 'reload', 'connections'])) { + require_once __DIR__ . '/support/bootstrap.php'; +} else { + if (class_exists('Support\App')) { + Support\App::loadAllConfig(['route']); + } else { + Config::reload(config_path(), ['route', 'container']); + } +} + +$cli = new Command(); +$cli->setName('webman cli'); +$cli->installInternalCommands(); +if (is_dir($command_path = Util::guessPath(app_path(), '/command', true))) { + $cli->installCommands($command_path); +} + +foreach (config('plugin', []) as $firm => $projects) { + if (isset($projects['app'])) { + foreach (['', '/app'] as $app) { + if ($command_str = Util::guessPath(base_path() . "/plugin/$firm{$app}", 'command')) { + $command_path = base_path() . "/plugin/$firm{$app}/$command_str"; + $cli->installCommands($command_path, "plugin\\$firm" . str_replace('/', '\\', $app) . "\\$command_str"); + } + } + } + foreach ($projects as $name => $project) { + if (!is_array($project)) { + continue; + } + $project['command'] ??= []; + array_walk($project['command'], [$cli, 'createCommandInstance']); + } +} + +$cli->run();