|
@@ -0,0 +1,480 @@
|
|
|
|
|
+<?php
|
|
|
|
|
+
|
|
|
|
|
+namespace App\Jobs\Manager\CollectData\Statistics;
|
|
|
|
|
+
|
|
|
|
|
+use Illuminate\Bus\Queueable;
|
|
|
|
|
+use Illuminate\Contracts\Queue\ShouldBeUnique;
|
|
|
|
|
+use Illuminate\Contracts\Queue\ShouldQueue;
|
|
|
|
|
+use Illuminate\Foundation\Bus\Dispatchable;
|
|
|
|
|
+use Illuminate\Queue\InteractsWithQueue;
|
|
|
|
|
+use Illuminate\Queue\SerializesModels;
|
|
|
|
|
+use App\Models\Manager\Collect\Product as ProductModel;
|
|
|
|
|
+use App\Models\Manager\Process\LowPriceGoods as ProcessLowPriceGoodsModel;
|
|
|
|
|
+use App\Models\Manager\Process\ScrapeData as ScrapeDataModel;
|
|
|
|
|
+use App\Models\Manager\WashConfig\LowPriceGoods as WashConfigLowPriceGoodsModel;
|
|
|
|
|
+use App\Models\Manager\Process\ViolationProduct as ProcessViolationProductModel;
|
|
|
|
|
+use App\Models\Manager\WashConfig\ViolationProduct as WashConfigViolationProductModel;
|
|
|
|
|
+use App\Models\Manager\Process\ViolationStore as ProcessViolationStoreModel;
|
|
|
|
|
+use App\Models\Manager\WashConfig\ViolationStore as WashConfigViolationStoreModel;
|
|
|
|
|
+use App\Models\Manager\Collect\SurveillanceWarningNotice as SurveillanceWarningNoticeModel;
|
|
|
|
|
+use App\Jobs\Manager\Process\SendEmailJobs;
|
|
|
|
|
+use Illuminate\Support\Facades\Cache;
|
|
|
|
|
+use App\Facades\Servers\Logs\Log;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 商品数据-监控采集清洗情况
|
|
|
|
|
+ * @author: 唐远望
|
|
|
|
|
+ * @version: 1.0
|
|
|
|
|
+ * @date: 2026-06-03
|
|
|
|
|
+ */
|
|
|
|
|
+class ProductSurveillanceJobs implements ShouldQueue
|
|
|
|
|
+{
|
|
|
|
|
+ use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
|
|
|
+ public $tries = 3; // 限制重试次数
|
|
|
|
|
+ public $timeout = 600; // 10分钟超时
|
|
|
|
|
+
|
|
|
|
|
+ protected $message_data;
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Create a new job instance.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @return void
|
|
|
|
|
+ */
|
|
|
|
|
+ public function __construct(array $message_data)
|
|
|
|
|
+ {
|
|
|
|
|
+ $this->message_data = $message_data;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Execute the job.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @return void
|
|
|
|
|
+ */
|
|
|
|
|
+ public function handle()
|
|
|
|
|
+ {
|
|
|
|
|
+ try {
|
|
|
|
|
+ $ProductModel = new ProductModel();
|
|
|
|
|
+ $ScrapeDataModel = new ScrapeDataModel();
|
|
|
|
|
+ $ProcessLowPriceGoodsModel = new ProcessLowPriceGoodsModel();
|
|
|
|
|
+ $WashConfigLowPriceGoodsModel = new WashConfigLowPriceGoodsModel();
|
|
|
|
|
+ $ProcessViolationProductModel = new ProcessViolationProductModel();
|
|
|
|
|
+ $WashConfigViolationProductModel = new WashConfigViolationProductModel();
|
|
|
|
|
+ $ProcessViolationStoreModel = new ProcessViolationStoreModel();
|
|
|
|
|
+ $WashConfigViolationStoreModel = new WashConfigViolationStoreModel();
|
|
|
|
|
+ $SurveillanceWarningNoticeModel = new SurveillanceWarningNoticeModel();
|
|
|
|
|
+ //获取已经开启邮箱通知的用户
|
|
|
|
|
+ $email_notice_list = $SurveillanceWarningNoticeModel->where([['email', '!=', '']])->where('status', 0)->get()->toarray();
|
|
|
|
|
+ if (empty($email_notice_list)) return true;
|
|
|
|
|
+
|
|
|
|
|
+ $company_id = isset($this->message_data['company_id']) ? $this->message_data['company_id'] : 0; //品牌方公司ID
|
|
|
|
|
+ $company_info = $this->message_data['company_info'];
|
|
|
|
|
+ $start_time = $this->message_data['start_time'];
|
|
|
|
|
+ $end_time = $this->message_data['end_time'];
|
|
|
|
|
+ $start_time_string = date('Y-m-d H:i:s', $start_time);
|
|
|
|
|
+ $end_time_string = date('Y-m-d H:i:s', $end_time);
|
|
|
|
|
+
|
|
|
|
|
+ $key_name = 'LowPriceProductSurveillanceJobs_' . $company_id . '_' . $start_time . '_' . $end_time;
|
|
|
|
|
+ $is_end_select = Cache::get($key_name);
|
|
|
|
|
+ if (!empty($is_end_select)) return true;
|
|
|
|
|
+ $config_collect_product_content = ''; //配置采集商品任务:
|
|
|
|
|
+ $actual_collect_product_content = ''; //实际采集到的商品:
|
|
|
|
|
+ $config_low_product_content = ''; //配置的低价清洗商品:
|
|
|
|
|
+ $cleaned_low_product_content = ''; //实际清洗出来的低价商品:
|
|
|
|
|
+ $config_violation_product_content = ''; //配置的违规清洗商品:
|
|
|
|
|
+ $cleaned_violation_product_content = ''; //实际清洗出来的违规商品:
|
|
|
|
|
+ $config_violation_store_content = ''; //配置的违规店铺:
|
|
|
|
|
+ $cleaned_violation_store_content = ''; //实际清洗出来的违规店铺:
|
|
|
|
|
+
|
|
|
|
|
+ $current_time = time();
|
|
|
|
|
+ // 获取采集时间是星期几
|
|
|
|
|
+ $now_week = date('w', $current_time);
|
|
|
|
|
+ if ($now_week == 0) {
|
|
|
|
|
+ $now_week = 7;
|
|
|
|
|
+ }
|
|
|
|
|
+ //配置采集商品任务商品查询
|
|
|
|
|
+ $where = [];
|
|
|
|
|
+ $where[] = ['company_id', '=', $company_id];
|
|
|
|
|
+ $where[] = ['status', '=', 0];
|
|
|
|
|
+ $need_config_product_data = [];
|
|
|
|
|
+ $config_product_data_info = $ProductModel->WhereRaw("FIND_IN_SET(?, sampling_cycle)", [$now_week])->select(['product_name', 'sampling_start_time', 'sampling_end_time'])->where($where)->get()->toArray();
|
|
|
|
|
+ if (empty($config_product_data_info)) {
|
|
|
|
|
+ $config_collect_product_content .= '无' . ';';
|
|
|
|
|
+ //没有采集任务不处理
|
|
|
|
|
+ return true;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ $config_collect_product_list = [];
|
|
|
|
|
+ foreach ($config_product_data_info as $product_name) {
|
|
|
|
|
+ $sampling_start_time = $product_name['sampling_start_time'];
|
|
|
|
|
+ $sampling_end_time = $product_name['sampling_end_time'];
|
|
|
|
|
+ if ($sampling_start_time != '' && $sampling_end_time != '' && ($current_time < $sampling_start_time || $current_time > $sampling_end_time)) {
|
|
|
|
|
+ continue; //如果当前时间小于采集开始时间或者大于采集结束时间则跳过
|
|
|
|
|
+ }
|
|
|
|
|
+ $need_config_product_data[$product_name['product_name']] = $product_name;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (empty($need_config_product_data)) {
|
|
|
|
|
+ return true; //没有采集任务不处理
|
|
|
|
|
+ }
|
|
|
|
|
+ foreach ($need_config_product_data as $product_name) {
|
|
|
|
|
+ $config_collect_product_content .= $product_name['product_name'] . ';';
|
|
|
|
|
+ //实际采集到的商品
|
|
|
|
|
+ $where = [];
|
|
|
|
|
+ $where[] = ['insert_time', '>=', $start_time_string];
|
|
|
|
|
+ $where[] = ['insert_time', '<=', $end_time_string];
|
|
|
|
|
+ $where[] = ['enterprise_id', '=', $company_id];
|
|
|
|
|
+ $where[] = ['product_name', 'like', '%' . $product_name['product_name'] . '%']; // 增加模糊匹配
|
|
|
|
|
+ $actual_product_data_count = $ScrapeDataModel->where($where)->count();
|
|
|
|
|
+ if ($actual_product_data_count < 1) {
|
|
|
|
|
+ $actual_collect_product_content .= '无' . ';';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ $actual_collect_product_content .= $product_name['product_name'] . ':数量' . $actual_product_data_count . ';';
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ //配置的低价清洗商品
|
|
|
|
|
+ $where = [];
|
|
|
|
|
+ $where[] = ['company_id', '=', $company_id];
|
|
|
|
|
+ $where[] = ['status', '=', 0];
|
|
|
|
|
+ $config_low_product_data_info = $WashConfigLowPriceGoodsModel->select('product_name')->where($where)->groupBy('product_name')->get()->toArray();
|
|
|
|
|
+ if (empty($config_low_product_data_info)) {
|
|
|
|
|
+ $config_low_product_content .= '无' . ';';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ foreach ($config_low_product_data_info as $product_name) {
|
|
|
|
|
+ $config_low_product_content .= $product_name['product_name'] . ';';
|
|
|
|
|
+ //实际清洗出来的低价商品
|
|
|
|
|
+ $where = [];
|
|
|
|
|
+ $where[] = ['insert_time', '>=', strtotime($start_time_string)];
|
|
|
|
|
+ $where[] = ['insert_time', '<=', strtotime($end_time_string)];
|
|
|
|
|
+ $where[] = ['company_id', '=', $company_id];
|
|
|
|
|
+ $where[] = ['product_name', 'like', '%' . $product_name['product_name'] . '%']; // 增加模糊匹配
|
|
|
|
|
+ $cleaned_product_data_count = $ProcessLowPriceGoodsModel->where($where)->count();
|
|
|
|
|
+ if ($cleaned_product_data_count < 1) {
|
|
|
|
|
+ $cleaned_low_product_content .= '无' . ';';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ $cleaned_low_product_content .= $product_name['product_name'] . ':数量' . $cleaned_product_data_count . ';';
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ //配置的违规清洗商品
|
|
|
|
|
+ $where = [];
|
|
|
|
|
+ $where[] = ['company_id', '=', $company_id];
|
|
|
|
|
+ $where[] = ['insert_time', '>=', $start_time_string];
|
|
|
|
|
+ $config_violation_product_data_info = $WashConfigViolationProductModel->select('product_name')->where($where)->groupBy('product_name')->get()->toArray();
|
|
|
|
|
+ if (empty($config_violation_product_data_info)) {
|
|
|
|
|
+ $config_violation_product_content .= '无' . ';';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ foreach ($config_violation_product_data_info as $product_name) {
|
|
|
|
|
+ $config_violation_product_content .= $product_name['product_name'] . ';';
|
|
|
|
|
+ //实际清洗出来的违规商品
|
|
|
|
|
+ $where = [];
|
|
|
|
|
+ $where[] = ['insert_time', '>=', strtotime($start_time_string)];
|
|
|
|
|
+ $where[] = ['insert_time', '<=', strtotime($end_time_string)];
|
|
|
|
|
+ $where[] = ['company_id', '=', $company_id];
|
|
|
|
|
+ $where[] = ['product_name', 'like', '%' . $product_name['product_name'] . '%']; // 增加模糊匹配
|
|
|
|
|
+ $cleaned_product_data_count = $ProcessViolationProductModel->where($where)->count();
|
|
|
|
|
+ if ($cleaned_product_data_count < 1) {
|
|
|
|
|
+ $cleaned_violation_product_content .= '无' . ';';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ $cleaned_violation_product_content .= $product_name['product_name'] . ':数量' . $cleaned_product_data_count . ';';
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ //配置的违规店铺
|
|
|
|
|
+ $where = [];
|
|
|
|
|
+ $where[] = ['company_id', '=', $company_id];
|
|
|
|
|
+ $where[] = ['insert_time', '<=', $end_time_string];
|
|
|
|
|
+ $config_violation_store_data_info = $WashConfigViolationStoreModel->select('store_name')->where($where)->groupBy('store_name')->get()->toArray();
|
|
|
|
|
+ if (empty($config_violation_store_data_info)) {
|
|
|
|
|
+ $config_violation_store_content .= '无' . ';';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ foreach ($config_violation_store_data_info as $store_name) {
|
|
|
|
|
+ $config_violation_store_content .= $store_name['store_name'] . ';';
|
|
|
|
|
+ //实际清洗出来的违规店铺
|
|
|
|
|
+ $where = [];
|
|
|
|
|
+ $where[] = ['insert_time', '>=', strtotime($start_time_string)];
|
|
|
|
|
+ $where[] = ['insert_time', '<=', strtotime($end_time_string)];
|
|
|
|
|
+ $where[] = ['company_id', '=', $company_id];
|
|
|
|
|
+ $where[] = ['store_name', 'like', '%' . $store_name['store_name'] . '%']; // 增加模糊匹配
|
|
|
|
|
+ $cleaned_store_data_count = $ProcessViolationStoreModel->where($where)->count();
|
|
|
|
|
+ if ($cleaned_store_data_count < 1) {
|
|
|
|
|
+ $cleaned_violation_store_content .= '无' . ';';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ $cleaned_violation_store_content .= $store_name['store_name'] . ':数量' . $cleaned_store_data_count . ';';
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ //合并展示内容
|
|
|
|
|
+ $email_title = '【' . $company_info['company_name'] . '】' . "数据监控-采集和清洗情况通知";
|
|
|
|
|
+ $email_content = $config_collect_product_content . $actual_collect_product_content . $config_low_product_content . $cleaned_low_product_content .
|
|
|
|
|
+ $config_violation_product_content . $cleaned_violation_product_content . $config_violation_store_content . $cleaned_violation_store_content;
|
|
|
|
|
+ $email_content_html = $this->buildEmailHtml(
|
|
|
|
|
+ $config_collect_product_content,
|
|
|
|
|
+ $actual_collect_product_content,
|
|
|
|
|
+ $config_low_product_content,
|
|
|
|
|
+ $cleaned_low_product_content,
|
|
|
|
|
+ $config_violation_product_content,
|
|
|
|
|
+ $cleaned_violation_product_content,
|
|
|
|
|
+ $config_violation_store_content,
|
|
|
|
|
+ $cleaned_violation_store_content
|
|
|
|
|
+ );
|
|
|
|
|
+ // echo $email_content_html; exit;
|
|
|
|
|
+ //发送预警通知
|
|
|
|
|
+ foreach ($email_notice_list as $email_notice) {
|
|
|
|
|
+ $message_data = [
|
|
|
|
|
+ 'email' => $email_notice['email'],
|
|
|
|
|
+ 'email_content' => json_encode(['title' => $email_title, 'content' => $email_content_html]),
|
|
|
|
|
+ ];
|
|
|
|
|
+ SendEmailJobs::dispatch(['notice_data_info' => $message_data]);
|
|
|
|
|
+ }
|
|
|
|
|
+ //记录日志
|
|
|
|
|
+ } catch (\Exception $e) {
|
|
|
|
|
+ print_r($e->getMessage());
|
|
|
|
|
+ exit;
|
|
|
|
|
+ Log::info('job_error', '低价商品数据数据-监控采集清洗情况队列失败', ['data' => $this->message_data, 'error' => $e->getMessage()]);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 构建美观的邮件HTML内容
|
|
|
|
|
+ */
|
|
|
|
|
+ private function buildEmailHtml(
|
|
|
|
|
+ $config_collect,
|
|
|
|
|
+ $actual_collect,
|
|
|
|
|
+ $config_low,
|
|
|
|
|
+ $cleaned_low,
|
|
|
|
|
+ $config_violation_product,
|
|
|
|
|
+ $cleaned_violation_product,
|
|
|
|
|
+ $config_violation_store,
|
|
|
|
|
+ $cleaned_violation_store
|
|
|
|
|
+ ) {
|
|
|
|
|
+ $html = '
|
|
|
|
|
+ <!DOCTYPE html>
|
|
|
|
|
+ <html>
|
|
|
|
|
+ <head>
|
|
|
|
|
+ <meta charset="UTF-8">
|
|
|
|
|
+ <style>
|
|
|
|
|
+ body {
|
|
|
|
|
+ font-family: "Microsoft YaHei", Arial, sans-serif;
|
|
|
|
|
+ background-color: #f5f7fa;
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ padding: 20px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .email-container {
|
|
|
|
|
+ max-width: 800px;
|
|
|
|
|
+ margin: 0 auto;
|
|
|
|
|
+ background-color: #ffffff;
|
|
|
|
|
+ border-radius: 12px;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ box-shadow: 0 2px 12px rgba(0,0,0,0.1);
|
|
|
|
|
+ }
|
|
|
|
|
+ .email-header {
|
|
|
|
|
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
|
|
+ color: white;
|
|
|
|
|
+ padding: 30px 20px;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ }
|
|
|
|
|
+ .email-header h1 {
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ font-size: 24px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ }
|
|
|
|
|
+ .email-header p {
|
|
|
|
|
+ margin: 10px 0 0;
|
|
|
|
|
+ opacity: 0.9;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .email-body {
|
|
|
|
|
+ padding: 30px 25px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .section {
|
|
|
|
|
+ margin-bottom: 25px;
|
|
|
|
|
+ border-left: 4px solid;
|
|
|
|
|
+ padding: 12px 20px;
|
|
|
|
|
+ background-color: #f8f9fc;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .section-title {
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .section-title .icon {
|
|
|
|
|
+ font-size: 20px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .section-content {
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ color: #555;
|
|
|
|
|
+ line-height: 1.8;
|
|
|
|
|
+ word-break: break-all;
|
|
|
|
|
+ }
|
|
|
|
|
+ .product-item {
|
|
|
|
|
+ display: inline-block;
|
|
|
|
|
+ background: #e8ecf1;
|
|
|
|
|
+ padding: 4px 12px;
|
|
|
|
|
+ border-radius: 20px;
|
|
|
|
|
+ margin: 4px 6px 4px 0;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .product-item strong {
|
|
|
|
|
+ color: #667eea;
|
|
|
|
|
+ }
|
|
|
|
|
+ .empty-data {
|
|
|
|
|
+ color: #999;
|
|
|
|
|
+ font-style: italic;
|
|
|
|
|
+ }
|
|
|
|
|
+ .separator {
|
|
|
|
|
+ border-top: 1px dashed #e0e4e8;
|
|
|
|
|
+ margin: 20px 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ .email-footer {
|
|
|
|
|
+ background-color: #f8f9fc;
|
|
|
|
|
+ padding: 15px 25px;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: #999;
|
|
|
|
|
+ border-top: 1px solid #eef2f6;
|
|
|
|
|
+ }
|
|
|
|
|
+ .badge {
|
|
|
|
|
+ display: inline-block;
|
|
|
|
|
+ background: #667eea;
|
|
|
|
|
+ color: white;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ padding: 2px 8px;
|
|
|
|
|
+ border-radius: 12px;
|
|
|
|
|
+ margin-left: 8px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .stats-number {
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: #667eea;
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ }
|
|
|
|
|
+ </style>
|
|
|
|
|
+ </head>
|
|
|
|
|
+ <body>
|
|
|
|
|
+ <div class="email-container">
|
|
|
|
|
+ <div class="email-header">
|
|
|
|
|
+ <h1>数据监控报告</h1>
|
|
|
|
|
+ <p>采集与清洗情况通知</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="email-body">
|
|
|
|
|
+ ' . $this->buildSection('🎯 配置采集商品任务', 'config_collect', $config_collect, '#667eea') . '
|
|
|
|
|
+ ' . $this->buildSection('✅ 实际采集到的商品', 'actual_collect', $actual_collect, '#48bb78') . '
|
|
|
|
|
+
|
|
|
|
|
+ <div class="separator"></div>
|
|
|
|
|
+
|
|
|
|
|
+ ' . $this->buildSection('⚙️ 配置的低价清洗商品', 'config_low', $config_low, '#ed8936') . '
|
|
|
|
|
+ ' . $this->buildSection('🔍 实际清洗出来的低价商品', 'cleaned_low', $cleaned_low, '#ed8936') . '
|
|
|
|
|
+
|
|
|
|
|
+ <div class="separator"></div>
|
|
|
|
|
+
|
|
|
|
|
+ ' . $this->buildSection('⚠️ 配置的违规清洗商品', 'config_violation_product', $config_violation_product, '#e53e3e') . '
|
|
|
|
|
+ ' . $this->buildSection('🚫 实际清洗出来的违规商品', 'cleaned_violation_product', $cleaned_violation_product, '#e53e3e') . '
|
|
|
|
|
+
|
|
|
|
|
+ <div class="separator"></div>
|
|
|
|
|
+
|
|
|
|
|
+ ' . $this->buildSection('🏪 配置的违规店铺', 'config_violation_store', $config_violation_store, '#9b59b6') . '
|
|
|
|
|
+ ' . $this->buildSection('🚷 实际清洗出来的违规店铺', 'cleaned_violation_store', $cleaned_violation_store, '#9b59b6') . '
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="email-footer">
|
|
|
|
|
+ <p>此邮件由系统自动发送,请勿直接回复 | 数据统计时间:' . date('Y-m-d H:i:s') . '</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </body>
|
|
|
|
|
+ </html>';
|
|
|
|
|
+
|
|
|
|
|
+ return $html;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 构建单个数据区块
|
|
|
|
|
+ */
|
|
|
|
|
+ private function buildSection($title, $type, $content, $color)
|
|
|
|
|
+ {
|
|
|
|
|
+ $icon = $this->getIconByType($type);
|
|
|
|
|
+ $formattedContent = $this->formatContent($content);
|
|
|
|
|
+
|
|
|
|
|
+ return '
|
|
|
|
|
+ <div class="section" style="border-left-color: ' . $color . ';">
|
|
|
|
|
+ <div class="section-title">
|
|
|
|
|
+ <span class="icon"></span>
|
|
|
|
|
+ <span>' . $title . '</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="section-content">
|
|
|
|
|
+ ' . $formattedContent . '
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 格式化内容,将分号分隔的数据转换为美观的标签形式
|
|
|
|
|
+ */
|
|
|
|
|
+ private function formatContent($content)
|
|
|
|
|
+ {
|
|
|
|
|
+ // 如果内容为空或者只包含分号
|
|
|
|
|
+ if (empty($content) || trim($content) === ';' || trim($content) === '无;') {
|
|
|
|
|
+ return '<span class="empty-data">📭 暂无数据</span>';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 检查是否包含"无;"的情况
|
|
|
|
|
+ if (strpos($content, '无;') !== false && strlen($content) <= 6) {
|
|
|
|
|
+ return '<span class="empty-data">📭 暂无数据</span>';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 移除末尾的分号
|
|
|
|
|
+ $content = rtrim($content, ';');
|
|
|
|
|
+
|
|
|
|
|
+ // 按分号分割
|
|
|
|
|
+ $items = explode(';', $content);
|
|
|
|
|
+ $html = '';
|
|
|
|
|
+
|
|
|
|
|
+ foreach ($items as $item) {
|
|
|
|
|
+ $item = trim($item);
|
|
|
|
|
+ if (empty($item)) continue;
|
|
|
|
|
+
|
|
|
|
|
+ // 跳过"无"项
|
|
|
|
|
+ if ($item === '无') continue;
|
|
|
|
|
+
|
|
|
|
|
+ // 检查是否包含数量信息(格式:商品名:数量N)
|
|
|
|
|
+ if (preg_match('/^(.+):数量(\d+)$/u', $item, $matches)) {
|
|
|
|
|
+ $productName = trim($matches[1]);
|
|
|
|
|
+ $count = $matches[2];
|
|
|
|
|
+ $html .= '<span class="product-item"><strong>' . htmlspecialchars($productName) . '</strong> <span class="stats-number">' . $count . '</span> 件</span>';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 普通商品名
|
|
|
|
|
+ $html .= '<span class="product-item">' . htmlspecialchars($item) . '</span>';
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return $html ?: '<span class="empty-data">📭 暂无数据</span>';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 根据类型获取对应的图标
|
|
|
|
|
+ */
|
|
|
|
|
+ private function getIconByType($type)
|
|
|
|
|
+ {
|
|
|
|
|
+ $icons = [
|
|
|
|
|
+ 'config_collect' => '📋',
|
|
|
|
|
+ 'actual_collect' => '📦',
|
|
|
|
|
+ 'config_low' => '💰',
|
|
|
|
|
+ 'cleaned_low' => '💎',
|
|
|
|
|
+ 'config_violation_product' => '⚠️',
|
|
|
|
|
+ 'cleaned_violation_product' => '🚫',
|
|
|
|
|
+ 'config_violation_store' => '🏪',
|
|
|
|
|
+ 'cleaned_violation_store' => '🔨',
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ return $icons[$type] ?? '📌';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ public function failed(\Throwable $exception)
|
|
|
|
|
+ {
|
|
|
|
|
+ Log::info('job_error', '低价商品数据数据-监控采集清洗情况队列完全失败', ['data' => $this->message_data, 'error' => $exception->getMessage()]);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|