Selaa lähdekoodia

Update 优化邮箱查找逻辑,添加报告发送时间配置

YueYunyun 6 kuukautta sitten
vanhempi
commit
e0e069b5ea

+ 4 - 0
SourceCode/TenderCrawler/app/config.yml

@@ -56,6 +56,10 @@ schedule:
   collect: 06:00,22:00 # 每天采集数据时间
   process: 07:00,10:00 # 每天采集数据时间
   send_email: 8:20,14:00 # 每天发送邮件时间
+  send_current_month_report_day: 30 # 每月几号发送本月中标报告
+  send_current_month_report_time: 08:20 # 每月几号记点发送本月中标报告
+  send_prev_month_report_day: 1 # 每月几号发送上月中标报告
+  send_prev_month_report_time: 08:20 # 每月几号记点发送上月中标报告
   run_now: false
 selenium:
   remote_driver_url: http://127.0.0.1:3534/wd/hub

+ 92 - 66
SourceCode/TenderCrawler/app/main/data_process.py

@@ -21,6 +21,7 @@ class DataProcess:
     DEFAULT_AI_PROMPT_TEMPLATE_2 = """在以上内容中提取信息:
             编号(no) 、标题(title)、公告时间(date)、标中的总价格(price)、标中的公司,多个以逗号分割(bidder)、150-300字的标的物说明,标的物价格,公司的明细等内容摘要(summary),设备(devices)。
             提取出相关设备的名称信息,多个设备以逗号分割。返回包含no,title,date,price,bidder,summary字段的json格式字符串,没有找到或未提供的信息json字段为空  """
+
     def __init__(self, store: IDataStore):
         self._store = store
         self._ai_system_prompt = self.config.get("ai.system_prompt",
@@ -46,82 +47,107 @@ class DataProcess:
             raise Exception(f"数据处理发生异常: {e}")
 
     def _process_item(self, url: str) -> None:
-       try:
-           self.logger.info(f"START ==>URL:{url}")
-           item = self.store.query_one_collect_by_url(url)
-           if not item:
-               self.logger.info(f"END==> NOT FOUND URL:{url}")
-               return
-           if item.status == 1:
-               self.logger.info(f"ALREADY1 URL:{url}")
-               return
-           data = self.store.query_one_process_by_url(url) if item.data_type == 0 else  self.store.query_one_process_result_by_url(url)
-           if data :
-               self.logger.info(f"ALREADY2 [{item.data_type}] URL==> {url}")
-               return
-           data =  self._ai_process_1(item) if item.data_type == 0 else self._ai_process_2(item)
-           if data:
-               old = None
-               if data.no:
-                   old = self.store.query_one_process_result_by_no(data.no) if item.data_type == 0 else self.store.query_one_process_by_no(data.no)
-               if not old:
-                   data.url = url
-                   data.keyword = item.keyword
-                   data.attach_path = item.attach_path
-                   if item.data_type == 0:
-                       self.store.insert_process_data(data)
-                   else:
-                       self.store.insert_process_result_data(data)
-               else:
-                   if old.url != url:
-                       if old.other_urls:
-                           old.other_urls += f",{url}"
-                       else:
-                           old.other_urls = url
-                       if item.data_type == 0:
-                          self.store.set_process_other_urls(data.url, old.other_urls)
-                       else:
-                          self.store.set_process_result_other_urls(data.url, old.other_urls)
-                   self.logger.info(f"ALREADY 编号: {data.no} URL:{old.other_urls}")
+        try:
+            self.logger.info(f"START ==>URL:{url}")
+            item = self.store.query_one_collect_by_url(url)
+            if not item:
+                self.logger.info(f"END==> NOT FOUND URL:{url}")
+                return
+            if item.status == 1:
+                self.logger.info(f"ALREADY1 URL:{url}")
+                return
+            data = self.store.query_one_process_by_url(
+                url
+            ) if item.data_type == 0 else self.store.query_one_process_result_by_url(
+                url)
+            if data:
+                self.logger.info(f"ALREADY2 [{item.data_type}] URL==> {url}")
+                return
+            data = self._ai_process_1(
+                item) if item.data_type == 0 else self._ai_process_2(item)
+            if data:
+                old = None
+                if data.no:
+                    old = self.store.query_one_process_result_by_no(
+                        data.no
+                    ) if item.data_type == 0 else self.store.query_one_process_by_no(
+                        data.no)
+                if not old:
+                    data.url = url
+                    data.keyword = item.keyword
+                    data.attach_path = item.attach_path
+                    if item.data_type == 0:
+                        self.store.insert_process_data(data)
+                    else:
+                        self.store.insert_process_result_data(data)
+                else:
+                    if old.url != url:
+                        if old.other_urls:
+                            old.other_urls += f",{url}"
+                        else:
+                            old.other_urls = url
+                        if item.data_type == 0:
+                            self.store.set_process_other_urls(
+                                data.url, old.other_urls)
+                        else:
+                            self.store.set_process_result_other_urls(
+                                data.url, old.other_urls)
+                    self.logger.info(
+                        f"ALREADY 编号: {data.no} URL:{old.other_urls}")
 
-           self.logger.info("END   ==>" + url)
-       except Exception as e:
-           self.logger.error(f"数据处理发生异常: {url} {e}")
+            self.logger.info("END   ==>" + url)
+        except Exception as e:
+            self.logger.error(f"数据处理发生异常: {url} {e}")
 
     def _ai_process_1(self, item: CollectData) -> ProcessData | None:
         try:
-            data = AiHelper().call_openai(self._ai_system_prompt,f"{item.content} {self._ai_prompt_template_1}")
-            return ProcessData(no=data.get("no"),
-                               title=data.get("title"),
-                               date=data.get("date"),
-                               area=data.get("area"),
-                               address=data.get("address"),
-                               devices=data.get("devices"),
-                               summary=data.get("summary"),
-                               release_date=data.get("release_date"),
-                               prompt_tokens=data.get("prompt_tokens"),
-                               completion_tokens=data.get("completion_tokens"),
-                               total_tokens=data.get("total_tokens"),
-                               )
+            data = AiHelper().call_openai(
+                self._ai_system_prompt,
+                f"{item.content} {self._ai_prompt_template_1}")
+            area_str = data.get("area")
+
+            if "省" in area_str:
+                area_str_arr = area_str.split("省")
+                area_str = area_str_arr[1] if len(
+                    area_str_arr) > 1 else area_str_arr[0]
+            if "市" in area_str:
+                area_str_arr = area_str.split("市")
+                area_str = area_str_arr[1] if len(
+                    area_str_arr) > 1 else area_str_arr[0]
+
+            return ProcessData(
+                no=data.get("no"),
+                title=data.get("title"),
+                date=data.get("date"),
+                area=area_str,
+                address=data.get("address"),
+                devices=data.get("devices"),
+                summary=data.get("summary"),
+                release_date=data.get("release_date"),
+                prompt_tokens=data.get("prompt_tokens"),
+                completion_tokens=data.get("completion_tokens"),
+                total_tokens=data.get("total_tokens"),
+            )
         except Exception as e:
             self.logger.error(f"AI 提取数据失败1: {item.url} {e}")
             return None
 
-
     def _ai_process_2(self, item: CollectData) -> ProcessResultData | None:
         try:
-            data = AiHelper().call_openai(self._ai_system_prompt,f"{item.content} {self._ai_prompt_template_2}")
-            return ProcessResultData(no=data.get("no"),
-                               title=data.get("title"),
-                               date=data.get("date"),
-                               price=data.get("price"),
-                               bidder=data.get("bidder"),
-                               summary=data.get("summary"),
-                               prompt_tokens=data.get("prompt_tokens"),
-                               completion_tokens=data.get("completion_tokens"),
-                               total_tokens=data.get("total_tokens"),
-                               )
+            data = AiHelper().call_openai(
+                self._ai_system_prompt,
+                f"{item.content} {self._ai_prompt_template_2}")
+            return ProcessResultData(
+                no=data.get("no"),
+                title=data.get("title"),
+                date=data.get("date"),
+                price=data.get("price"),
+                bidder=data.get("bidder"),
+                summary=data.get("summary"),
+                prompt_tokens=data.get("prompt_tokens"),
+                completion_tokens=data.get("completion_tokens"),
+                total_tokens=data.get("total_tokens"),
+            )
         except Exception as e:
             self.logger.error(f"AI 提取数据失败2: {item.url} {e}")
             return None
-

+ 49 - 13
SourceCode/TenderCrawler/app/main/data_send.py

@@ -1,4 +1,4 @@
-from datetime import datetime, timedelta
+from datetime import datetime
 import calendar
 
 from utils.logger_helper import LoggerHelper
@@ -11,6 +11,8 @@ from models.process_result_data import ProcessResultData
 class DataSend:
     logger = LoggerHelper.get_logger()
     _error_arr = []
+    _email_area_arr = []
+    _email_area_virtual_arr = []
 
     @property
     def store(self) -> IDataStore:
@@ -18,6 +20,8 @@ class DataSend:
 
     def __init__(self, store: IDataStore):
         self._store = store
+        self._email_area_arr = self.store.query_all_emails()
+        self._email_area_virtual_arr = self.store.query_all_virtual_emails()
 
     def send(self) -> None:
         self._error_arr = []
@@ -28,33 +32,32 @@ class DataSend:
         if len(self._error_arr) > 0:
             self._send_email_no_found()
 
-
     def send_report_current_month(self):
         # 查询当月的数据
-        start_date ,end_date = self._get_first_and_last_day_of_current_month()
-        self._send_reports(start_date,end_date)
+        start_date, end_date = self._get_first_and_last_day_of_current_month()
+        self._send_reports(start_date, end_date)
 
     def send_report_prev_month(self):
         # 查询上月的数据
-        start_date ,end_date = self._get_first_and_last_day_of_prev_month()
-        self._send_reports(start_date,end_date)
+        start_date, end_date = self._get_first_and_last_day_of_prev_month()
+        self._send_reports(start_date, end_date)
 
-    def _send_reports(self,start_date,end_date):
+    def _send_reports(self, start_date, end_date):
         self.logger.info(f"开始发送中标报告邮件,开始日期:{start_date.strftime("%Y-%m-%d")},结束日期:{end_date.strftime("%Y-%m-%d")}")
         email = self.store.query_master_email()
         if not email:
-            self.logger.error(f"没有找到master email")
+            self.logger.error("没有找到master email")
             return
-        items = self.store.query_to_report_by_date(start_date,end_date)
+        items = self.store.query_to_report_by_date(start_date, end_date)
         title = f"{start_date.month}月中标结果报告"
         body = self._build_report_email_html(title, items)
-        flag= EmailHelper().send_email(email, title, body,True)
+        flag = EmailHelper().send_email(email, title, body, True)
         if flag:
-            self.logger.info(f"发送中标报告邮件成功")
+            self.logger.info("发送中标报告邮件成功")
 
     def _send_item(self, item: ProcessData) -> None:
         self.logger.info(f"开始发送邮件,地区为:{item.area} ,URL为 {item.url}")
-        email = self.store.get_email_by_area(item.area)
+        email = self._get_email_by_area(item.area)
         if not email:
             self.logger.error(f"{item.area} 下没有找到email")
             if item.area not in self._error_arr:
@@ -65,6 +68,38 @@ class DataSend:
         if flag:
             self.store.set_send(item.no)
 
+    def _get_email_by_area(self, area: str, count: int = 0, virtual_area: str = None) -> str:
+        email = None
+        area_str = area
+        # if "省" in area:
+        #     area_str_arr = area.split("省")
+        #     area_str = area_str_arr[1] if len(area_str) > 1 else area_str_arr[0]
+        # if "市" in area:
+        #     area_str_arr = area.split("市")
+        #     area_str = area_str_arr[1] if len(area_str) > 1 else area_str_arr[0]
+        for area_item in self._email_area_arr:
+            if area_str in area_item.area:
+                email = area_item.email
+                if virtual_area:
+                    new_area = f"{area_item.area},{virtual_area}"
+                    self.store.update_area_email_area_by_name(area_item.name, new_area)
+                    self._email_area_arr = self.store.query_all_emails()
+                break
+        if not email and count < 3:
+            area_name = self._get_email_by_area_virtual(area_str)
+            if area_name:
+                virtual_area = f"{area_str},{virtual_area}" if virtual_area else area_str
+                email = self._get_email_by_area(area_name, count + 1, virtual_area)
+        return email
+
+    def _get_email_by_area_virtual(self, area: str) -> str:
+        name = None
+        for area_item in self._email_area_virtual_arr:
+            if area in area_item.area:
+                name = area_item.name
+                break
+        return name
+
     @staticmethod
     def _build_email_html(item: ProcessData, other: str = "") -> str:
         html_body = f"""
@@ -264,6 +299,7 @@ class DataSend:
         _, last_day = calendar.monthrange(today.year, today.month)
         last_day_of_current_month = datetime(today.year, today.month, last_day, 23, 59, 59)
         return first_day_of_current_month, last_day_of_current_month
+
     @staticmethod
     def _get_first_and_last_day_of_prev_month():
         # 获取当前日期
@@ -276,7 +312,7 @@ class DataSend:
             prev_month_year = today.year
             prev_month = today.month - 1
         # 获取上个月的第一天
-        first_day_prev_month = datetime(prev_month_year, prev_month, 1,0,0,0)
+        first_day_prev_month = datetime(prev_month_year, prev_month, 1, 0, 0, 0)
         # 获取上个月的最后一天
         _, last_day = calendar.monthrange(prev_month_year, prev_month)
         last_day_of_prev_month = datetime(prev_month_year, prev_month, last_day, 23, 59, 59)

+ 41 - 8
SourceCode/TenderCrawler/app/main/runner.py

@@ -11,6 +11,7 @@ from main.data_process import DataProcess
 from main.data_send import DataSend
 from utils.email_helper import EmailHelper
 
+
 class Runner:
     logger = LoggerHelper.get_logger()
     config = ConfigHelper()
@@ -29,7 +30,8 @@ class Runner:
             process_time = self.config.get("schedule.process")
             send_email_time = self.config.get("schedule.send_email")
 
-            collect_times = self._validate_and_format_time(collect_time, ["06:00"])
+            collect_times = self._validate_and_format_time(
+                collect_time, ["06:00"])
             for time in collect_times:
                 self.logger.info(f"{time} 执行 采集处理数据 任务")
                 schedule.every().day.at(time).do(self._collect_process_job)
@@ -46,7 +48,19 @@ class Runner:
                 self.logger.info(f"{time} 执行  发送邮件   任务")
                 schedule.every().day.at(time).do(self._send_job)
 
-            schedule.every().day.at("08:00").do(self._send_prev_month_report_job)
+            if self.config.get_int("schedule.send_current_month_report_day")>0:
+                report_time = self.config.get("schedule.send_current_month_report_time")
+                times = self._validate_and_format_time(report_time,["08:20"])
+                for time in times:
+                    self.logger.info(f"每月{self._get_current_month_report_day()}日 {time} 执行  发送当月报告   任务")
+                    schedule.every().day.at(time).do(self._send_prev_month_report_job)
+
+            if self.config.get_int("schedule.send_prev_month_report_day")>0:
+                report_time = self.config.get("schedule.send_prev_month_report_time")
+                times = self._validate_and_format_time(report_time, ["08:20"])
+                for time in times:
+                    self.logger.info(f"每月{self._get_prev_month_report_day()}日 {time} 执行  发送上月报告   任务")
+                    schedule.every().day.at(time).do(self._send_prev_month_report_job)
 
             if self.config.get_bool("schedule.run_now"):
                 self.logger.info("立即执行任务")
@@ -63,7 +77,7 @@ class Runner:
             self.logger.info("开始执行 数据采集处理 任务")
             url_setting = UrlSetting()
             for url_setting in url_setting.fetch_all():
-                data_collector =None
+                data_collector = None
                 try:
                     self.logger.info(f"开始采集: {url_setting.url}")
                     data_collector = DataCollector(url_setting.adapter_type,
@@ -122,16 +136,27 @@ class Runner:
 
     def _send_current_month_report_job(self):
         try:
-            self.logger.info("开始执行 邮件发送当月报告 任务")
-            DataSend(self.store).send_report_current_month()
-            self.logger.info("邮件发送当月报告 任务执行完毕")
+            if datetime.today().day == self._get_current_month_report_day():
+                self.logger.info("开始执行 邮件发送当月报告 任务")
+                DataSend(self.store).send_report_current_month()
+                self.logger.info("邮件发送当月报告 任务执行完毕")
         except Exception as e:
             self._send_error_email("邮件发送", f"\n    错误: {str(e)}")
             self.logger.error(f"邮件发送当月报告 任务执行失败: {e}")
 
+    def _get_current_month_report_day(self):
+        day = self.config.get_int("schedule.send_current_month_report_day",30)
+        if datetime.today().month==2 and day > 28 :
+            day = 28
+        if datetime.today().month in [4,6,9,11] and day > 30:
+            day = 30
+        if day > 31:
+            day = 31
+        return day
+
     def _send_prev_month_report_job(self):
         try:
-            if datetime.today().day == 1 :
+            if datetime.today().day == self._get_prev_month_report_day():
                 self.logger.info("开始执行 邮件发送上月报告 任务")
                 DataSend(self.store).send_report_prev_month()
                 self.logger.info("邮件发送上月报告 任务执行完毕")
@@ -139,7 +164,15 @@ class Runner:
             self._send_error_email("邮件发送", f"\n    错误: {str(e)}")
             self.logger.error(f"邮件发送上月报告 任务执行失败: {e}")
 
-
+    def _get_prev_month_report_day(self):
+        day = self.config.get_int("schedule.send_prev_month_report_day",1)
+        if datetime.today().month == 2 and day > 28:
+            day = 28
+        if datetime.today().month in [4, 6, 9, 11] and day > 30:
+            day = 30
+        if day > 31:
+            day = 31
+        return day
     def _validate_and_format_time(self, time_str, default_time: list):
         """验证并格式化时间字符串"""
         if not time_str:

+ 24 - 2
SourceCode/TenderCrawler/app/models/area_email.py

@@ -3,13 +3,21 @@ from utils.mysql_helper import MySQLHelper
 
 class AreaEmail:
 
-    def __init__(self, name=None, area=None, email=None,is_active=None,remark=None):
+    def __init__(self,
+                 name=None,
+                 area=None,
+                 email=None,
+                 is_virtual=None,
+                 is_active=None,
+                 remark=None):
         self.name = name
         self.area = area
         if email is None:
             email = ""
         self.email = email.replace(",", ",")
+        self.is_virtual = is_virtual
         self.is_active = is_active
+
         self.remark = remark
 
     def __repr__(self):
@@ -35,9 +43,11 @@ class AreaEmail:
     #                   area_email.remark)
     #         db_helper.execute_non_query(query, params)
 
-    _query = "SELECT name,area,email FROM t_area_email WHERE is_active = 1"
+    _query = "SELECT name,area,email FROM t_area_email WHERE is_virtual = 0 and is_active = 1"
+    _query_virtual = "SELECT name,area,email FROM t_area_email WHERE is_virtual = 1 and is_active = 1"
     _query_master = "SELECT email FROM t_area_email WHERE name='master' AND is_active = 1"
     _query_by_area = "SELECT email FROM t_area_email WHERE CONCAT(area,',') like %s AND is_active = 1"
+
     # 查询 AreaEmail 数据
     def fetch_all(self):
         with MySQLHelper() as db_helper:
@@ -45,6 +55,12 @@ class AreaEmail:
             data = [AreaEmail(**result) for result in results]
             return data
 
+    def fetch_all_virtual(self):
+        with MySQLHelper() as db_helper:
+            results = db_helper.execute_query(self._query_virtual)
+            data = [AreaEmail(**result) for result in results]
+            return data
+
     def fetch_one_by_area(self, area: str):
         with MySQLHelper() as db_helper:
             params = ('%' + area + ',%', )
@@ -59,3 +75,9 @@ class AreaEmail:
             if result is None:
                 return None
             return result["email"]
+
+    def update_area_email_area_by_name(self, name: str, area: str):
+        with MySQLHelper() as db_helper:
+            query = "UPDATE t_area_email SET area = %s WHERE name = %s"
+            params = (area, name)
+            db_helper.execute_non_query(query, params)

+ 15 - 2
SourceCode/TenderCrawler/app/stores/data_store_interface.py

@@ -1,4 +1,6 @@
 from abc import ABC, abstractmethod
+
+from models.area_email import AreaEmail
 from models.collect_data import CollectData
 from models.process_data import ProcessData
 from models.process_result_data import ProcessResultData
@@ -58,7 +60,9 @@ class IDataStore(ABC):
         raise NotImplementedError("query_one_process_result_by_no 应由子类重写。")
 
     @abstractmethod
-    def insert_process_result_data(self, data: ProcessResultData, is_batch=True):
+    def insert_process_result_data(self,
+                                   data: ProcessResultData,
+                                   is_batch=True):
         raise NotImplementedError("insert_process_result_data 应由子类重写。")
 
     @abstractmethod
@@ -68,6 +72,7 @@ class IDataStore(ABC):
     @abstractmethod
     def set_process_result_other_urls(self, url, other_urls: str):
         raise NotImplementedError("set_process_result_other_urls 应由子类重写。")
+
     @abstractmethod
     def query_to_send(self):
         raise NotImplementedError("query_to_send 应由子类重写。")
@@ -81,9 +86,13 @@ class IDataStore(ABC):
         raise NotImplementedError("set_send 应由子类重写。")
 
     @abstractmethod
-    def get_emails(self) -> str:
+    def query_all_emails(self) -> list[AreaEmail]:
         raise NotImplementedError("get_emails 应由子类重写。")
 
+    @abstractmethod
+    def query_all_virtual_emails(self, area: str):
+        raise NotImplementedError("get_email_by_area 应由子类重写。")
+
     @abstractmethod
     def query_master_email(self) -> str:
         raise NotImplementedError("get_master_email 应由子类重写。")
@@ -91,3 +100,7 @@ class IDataStore(ABC):
     @abstractmethod
     def get_email_by_area(self, area: str):
         raise NotImplementedError("get_email_by_area 应由子类重写。")
+
+    @abstractmethod
+    def update_area_email_area_by_name(self, name: str, area: str):
+        raise NotImplementedError("update_area_email_area_by_name 应由子类重写。")

+ 27 - 23
SourceCode/TenderCrawler/app/stores/default_data_store.py

@@ -3,24 +3,18 @@ from utils.logger_helper import LoggerHelper
 from stores.data_store_interface import IDataStore
 
 
-class  DefaultDataStore(IDataStore):
-
-
-
-
-
-
+class DefaultDataStore(IDataStore):
 
     logger = LoggerHelper.get_logger()
 
     def __init__(self):
         pass
 
-    def query_one_collect_url(self, url: str) :
-        self.logger.info(f"Default: FETCH_ONE_URL")
-    def insert_collect_data(self, data , is_batch=True):
-        self.logger.info(f"Default: INSERT_COLLECT_DATA")
+    def query_one_collect_url(self, url: str):
+        self.logger.info("Default: FETCH_ONE_URL")
 
+    def insert_collect_data(self, data, is_batch=True):
+        self.logger.info("Default: INSERT_COLLECT_DATA")
 
     def save_collect_data(self, is_force=False):
         self.logger.info("Default: SAVE")
@@ -30,10 +24,13 @@ class  DefaultDataStore(IDataStore):
 
     def query_one_collect_by_url(self, url):
         self.logger.info("Default: QUERY_ONE_PROCESS")
+
     def query_one_process_by_url(self, no):
-        self.logger.info(f"Default: query_one_process_by_url")
+        self.logger.info("Default: query_one_process_by_url")
+
     def query_one_process_by_no(self, no):
-        self.logger.info(f"Default: query_one_process_by_no")
+        self.logger.info("Default: query_one_process_by_no")
+
     def insert_process_data(self, data):
         self.logger.info("Default: INSERT_PROCESS_DATA")
 
@@ -44,35 +41,42 @@ class  DefaultDataStore(IDataStore):
         self.logger.info("Default: SET_PROCESS_OTHER_URLS")
 
     def query_one_process_result_by_url(self, url):
-        self.logger.info(f"Default: QUERY_ONE_PROCESS_RESULT_BY_URL")
+        self.logger.info("Default: QUERY_ONE_PROCESS_RESULT_BY_URL")
 
     def query_one_process_result_by_no(self, no):
-        self.logger.info(f"Default: QUERY_ONE_PROCESS_RESULT_BY_NO")
+        self.logger.info("Default: QUERY_ONE_PROCESS_RESULT_BY_NO")
 
-    def insert_process_result_data(self, data: ProcessResultData, is_batch=True):
-        self.logger.info(f"Default: INSERT_PROCESS_RESULT_DATA")
+    def insert_process_result_data(self,
+                                   data: ProcessResultData,
+                                   is_batch=True):
+        self.logger.info("Default: INSERT_PROCESS_RESULT_DATA")
 
     def save_process_result_data(self, is_force=False):
-        self.logger.info(f"Default: SAVE_PROCESS_RESULT_DATA")
+        self.logger.info("Default: SAVE_PROCESS_RESULT_DATA")
 
     def set_process_result_other_urls(self, url, other_urls: str):
-        self.logger.info(f"Default: SET_PROCESS_RESULT_OTHER_URLS")
+        self.logger.info("Default: SET_PROCESS_RESULT_OTHER_URLS")
 
     def query_to_send(self):
         self.logger.info("Default: QUERY_TO_SEND")
+
     def query_to_report_by_date(self, start_date, end_date):
         self.logger.info("Default: QUERY_TO_REPORT_BY_DATE")
 
     def set_send(self, no: str):
         self.logger.info("Default: SET_SEND")
 
-    def get_emails(self) :
-        self.logger.info("Default: GET_EMAILS")
+    def query_all_emails(self):
+        self.logger.info("Default: QUERY_ALL_EMAILS")
 
+    def query_all_virtual_emails(self):
+        self.logger.info("Default: QUERY_ALL_VIRTUAL_EMAILS")
 
-    def query_master_email(self) :
+    def query_master_email(self):
         self.logger.info("Default: GET_MASTER_EMAIL")
 
-
     def get_email_by_area(self, area: str):
         self.logger.info("Default: GET_EMAIL_BY_AREA")
+
+    def update_area_email_area_by_name(self, name: str, area: str):
+        self.logger.info("Default: UPDATE_AREA_EMAIL_AREA_BY_NAME")

+ 30 - 14
SourceCode/TenderCrawler/app/stores/mysql_data_store.py

@@ -17,25 +17,28 @@ class MysqlDataStore(IDataStore):
     _areaEmail = AreaEmail()
 
     def __init__(self):
-        self._collect_size = self.config.get_int('save.collect_batch_size',1)
+        self._collect_size = self.config.get_int('save.collect_batch_size', 1)
         self._collect_list = []
-        self._process_size = self.config.get_int('save.process_batch_size',1)
+        self._process_size = self.config.get_int('save.process_batch_size', 1)
         self._process_list = []
         self._process_result_list = []
 
     def query_one_collect_url(self, url: str) -> str | None:
         return self._collectData.fetch_one_url(url)
+
     def insert_collect_data(self, data: CollectData, is_batch=True):
         if not is_batch:
             self._collectData.insert(data)
-            self.logger.info(f"保存 采集数据 到数据库: {data.url}" )
+            self.logger.info(f"保存 采集数据 到数据库: {data.url}")
         else:
             self._collect_list.append(data)
             self.save_collect_data()
 
     def save_collect_data(self, is_force=False):
-        if (is_force and len(self._collect_list)>0) or len(self._collect_list) >= self._collect_size:
-            self.logger.info("批量保存 采集数据 到数据库,数量: " + str(len(self._collect_list)))
+        if (is_force and len(self._collect_list) > 0) or len(
+                self._collect_list) >= self._collect_size:
+            self.logger.info("批量保存 采集数据 到数据库,数量: " +
+                             str(len(self._collect_list)))
             self._collectData.insert_batch(self._collect_list)
             self._collect_list = []
 
@@ -55,15 +58,17 @@ class MysqlDataStore(IDataStore):
         if not is_batch:
             self._processData.insert(data)
             self._collectData.set_process(data.url)
-            self.logger.info(f"保存 处理数据 到数据库: {data.url}" )
+            self.logger.info(f"保存 处理数据 到数据库: {data.url}")
         else:
             self._process_list.append(data)
             self.save_process_data()
 
     # 插入到数据库时会把CollectData设为已处理
     def save_process_data(self, is_force=False):
-        if (is_force and len(self._process_list)>0) or len(self._process_list) >= self._process_size:
-            self.logger.info(f"批量保存 处理数据 到数据库,数量: {str(len(self._process_list))}")
+        if (is_force and len(self._process_list) > 0) or len(
+                self._process_list) >= self._process_size:
+            self.logger.info(
+                f"批量保存 处理数据 到数据库,数量: {str(len(self._process_list))}")
             self._processData.insert_batch(self._process_list)
             urls = [item.url for item in self._process_list]
             self._collectData.set_process_list(urls)
@@ -78,18 +83,22 @@ class MysqlDataStore(IDataStore):
     def query_one_process_result_by_no(self, no):
         return self._processResultData.fetch_one_process_by_no(no)
 
-    def insert_process_result_data(self, data: ProcessResultData, is_batch=True):
+    def insert_process_result_data(self,
+                                   data: ProcessResultData,
+                                   is_batch=True):
         if not is_batch:
             self._processResultData.insert(data)
             self._collectData.set_process(data.url)
-            self.logger.info(f"保存 处理数据结果 到数据库: {data.url}" )
+            self.logger.info(f"保存 处理数据结果 到数据库: {data.url}")
         else:
             self._process_result_list.append(data)
             self.save_process_result_data()
 
     def save_process_result_data(self, is_force=False):
-        if (is_force and len(self._process_result_list)>0) or len(self._process_result_list) >= self._process_size:
-            self.logger.info(f"批量保存 处理数据结果 到数据库,数量: {str(len(self._process_result_list))}")
+        if (is_force and len(self._process_result_list) > 0) or len(
+                self._process_result_list) >= self._process_size:
+            self.logger.info(
+                f"批量保存 处理数据结果 到数据库,数量: {str(len(self._process_result_list))}")
             self._processResultData.insert_batch(self._process_result_list)
             urls = [item.url for item in self._process_result_list]
             self._collectData.set_process_list(urls)
@@ -105,16 +114,23 @@ class MysqlDataStore(IDataStore):
         return self._processData.fetch_no_send()
 
     def query_to_report_by_date(self, start_date, end_date):
-        return self._processResultData.fetch_to_report_by_date(start_date, end_date)
+        return self._processResultData.fetch_to_report_by_date(
+            start_date, end_date)
 
     def set_send(self, no: str):
         self._processData.set_send(no)
 
-    def get_emails(self) -> str:
+    def query_all_emails(self) -> list[AreaEmail]:
         return self._areaEmail.fetch_all()
 
+    def query_all_virtual_emails(self) -> list[AreaEmail]:
+        return self._areaEmail.fetch_all_virtual()
+
     def get_email_by_area(self, area: str) -> str:
         return self._areaEmail.fetch_one_by_area(area)
 
     def query_master_email(self) -> str:
         return self._areaEmail.fetch_master_email()
+
+    def update_area_email_area_by_name(self, name: str, area: str):
+        return self._areaEmail.update_area_email_area_by_name(name, area)

+ 0 - 2
SourceCode/TenderCrawler/docker-compose.yml

@@ -56,8 +56,6 @@ services:
       #      - APP_AI__KEY=
       #      - APP_AI__URL=http://192.168.0.109:7580/api/chat
       #      - APP_AI__MODEL=qwen2.5:7b
-      - APP_AI__MAX_TOKENS=1024
-      - APP_SCHEDULE__SLEEP_INTERVAL=600 #单位:秒 10分钟检查一次
       - APP_SCHEDULE__COLLECT=20:00,12:00
       - APP_SCHEDULE__PROCESS=23:00,4:00,13:00
       - APP_SCHEDULE__SEND_EMAIL=08:20,14:00

+ 53 - 21
SourceCode/TenderCrawler/init.sql

@@ -31,31 +31,63 @@ CREATE TABLE `t_area_email`  (
   `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '名称',
   `area` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '地区 多个以”,\"分隔',
   `email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '邮箱 多个以 ”,\" 分隔',
-  `is_active` int(4) NULL DEFAULT NULL COMMENT '激活状态 1:激活 0:失活',
+  `is_virtual` int(4) NULL DEFAULT 1 COMMENT '是否虚拟的,没有email的',
+  `is_active` int(4) NULL DEFAULT 0 COMMENT '激活状态 1:激活 0:失活',
   `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '备注',
   PRIMARY KEY (`name`) USING BTREE
 ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;
 
-INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_active`, `remark`) VALUES ('全国', '全国', 'chancelot@foxmail.com', 1, NULL);
-INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_active`, `remark`) VALUES ('宁波', '浙江省宁波市,浙江宁波,宁波市,宁波', '349977741@qq.com', 1, NULL);
-INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_active`, `remark`) VALUES ('武汉', '武汉市,武汉,中国武汉,中国武汉市', 'chancelot@foxmail.com,349977741@qq.com', 1, NULL);
-INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_active`, `remark`) VALUES ('济南', '江苏省济南市,江苏济南,济南市,济南', '349977741@qq.com', 1, NULL);
-INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_active`, `remark`) VALUES ('张志琼', '黑龙江,吉林,辽宁', 'zhiqiong.zhang@bruker.com', 0, NULL);
-INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_active`, `remark`) VALUES ('王双', '河北,山东济南,山东德州', 'shuang.wang@bruker.com', 0, NULL);
-INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_active`, `remark`) VALUES ('尚祖俭', '天津市,天津,中国天津,中国天津市', 'zujian.shang@bruker.com', 0, NULL);
-INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_active`, `remark`) VALUES ('赵跃', '北京', 'yue.zhao@bruker.com', 0, NULL);
-INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_active`, `remark`) VALUES ('张景灿', '陕西,新疆,宁夏,青海', 'jingcan.zhang@bruker.com', 0, NULL);
-INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_active`, `remark`) VALUES ('穆彦竹', '山西,河南,甘肃', 'yanzhu.mu@bruker.com', 0, NULL);
-INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_active`, `remark`) VALUES ('廖然', '内蒙古', 'ran.liao@bruker.com', 0, NULL);
-INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_active`, `remark`) VALUES ('吕小勇', '江苏', 'xiaoyong.lv@bruker.com', 0, NULL);
-INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_active`, `remark`) VALUES ('张潇', '浙江,福建', 'xiao.zhang@bruker.com', 0, NULL);
-INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_active`, `remark`) VALUES ('吴雪美', '上海', 'xuemei.wu@bruker.com', 0, NULL);
-INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_active`, `remark`) VALUES ('邬歆', '安徽,香港,澳门', 'xin.wu@bruker.com', 0, NULL);
-INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_active`, `remark`) VALUES ('冯新宝', '湖北,湖南', 'xinbao.feng@bruker.com', 0, NULL);
-INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_active`, `remark`) VALUES ('耿朝曦', '江西,贵州', 'zhaoxi.geng@bruker.com', 0, NULL);
-INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_active`, `remark`) VALUES ('李华斌', '广西,广东深圳', 'huabin.li@bruker.com', 0, NULL);
-INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_active`, `remark`) VALUES ('吕万明', '海南,广东广州,广东中山', 'wanming.lv@bruker.com', 0, NULL);
-INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_active`, `remark`) VALUES ('许建光', '西藏,云南,广东', 'jianguang.xu@bruker.com', 0, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('master', 'master', 'chancelot@foxmail.com,349977741@qq.com', 0,1, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('all', '全国', 'chancelot@foxmail.com,349977741@qq.com', 0,1, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('张志琼', '黑龙江,吉林,辽宁', 'zhiqiong.zhang@bruker.com', 0,0, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('王双', '河北,济南,山东德州', 'shuang.wang@bruker.com', 0,0, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('范国春', '山东', 'guochun.fan@bruker.com', 0,0, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('尚祖俭', '天津', 'zujian.shang@bruker.com', 0,0, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('赵跃', '北京', 'yue.zhao@bruker.com', 0,0, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('张景灿', '陕西,新疆,宁夏,青海', 'jingcan.zhang@bruker.com', 0,0, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('穆彦竹', '山西,河南,甘肃', 'yanzhu.mu@bruker.com', 0,0, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('廖然', '内蒙古', 'ran.liao@bruker.com', 0,0, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('吕小勇', '江苏', 'xiaoyong.lv@bruker.com', 0,0, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('张潇', '浙江,福建', 'xiao.zhang@bruker.com', 0,0, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('吴雪美', '上海', 'xuemei.wu@bruker.com', 0,0, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('邬歆', '安徽,香港,澳门', 'xin.wu@bruker.com', 0,0, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('冯新宝', '湖北,湖南', 'xinbao.feng@bruker.com', 0,0, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('耿朝曦', '江西,贵州', 'zhaoxi.geng@bruker.com', 0,0, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('李华斌', '广西,深圳', 'huabin.li@bruker.com', 0,0, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('吕万明', '海南,广州,中山', 'wanming.lv@bruker.com', 0,0, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('许建光', '西藏,云南,广东', 'jianguang.xu@bruker.com', 0,0, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('全国', '北京,天津,上海,重庆,河北,山西,黑龙江,吉林,辽宁,江苏,浙江,安徽,福建,江西,山东,河南,湖北,湖南,广东,海南,四川,贵州,云南,陕西,甘肃,青海,台湾,内蒙古,广西,西藏,宁夏,新疆,香港,澳门', '', 1, 1, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('北京', '东城区,西城区,朝阳区,丰台区,石景山区,海淀区,门头沟区,房山区,通州区,顺义区,大兴区,昌平区,平谷区,怀柔区,密云区,延庆区', '', 1, 1, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('上海', '黄浦区,徐汇区,长宁区,静安区,普陀区,虹口区,杨浦区,宝山区,闵行区,嘉定区,浦东新区,金山区,松江区,青浦区,奉贤区,崇明区', '', 1, 1, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('天津', '和平区,河东区,河西区,南开区,河北区,红桥区,东丽区,西青区,北辰区,武清区,宝坻区,滨海新区,宁河区,静海区,蓟州区', '', 1, 1, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('重庆', '渝中区,大渡口区,沙坪坝区,九龙坡区,南岸区,北碚区,渝北区,巴南区,长寿区,江北区,沙坪坝区,九龙坡区,南岸区,北碚区,渝北区,巴南区,长寿区,永川区,合川区,南川区,璧山区,江津区,合川区,永川区,南川区,璧山区,江津区,合川区,永川区,南川区,璧山区,江津区', '', 1, 1, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('江苏', '南京,苏州,无锡,常州,镇江,南通,扬州,盐城,连云港,淮安,宿迁,泰州,徐州', '', 1, 1, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('黑龙江','哈尔滨,齐齐哈尔,鸡西,鹤岗,双鸭山,大庆,伊春,佳木斯,七台河,牡丹江,黑河,绥化,大兴安岭地区', '', 1, 1, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('吉林', '长春,吉林,白山,延边朝鲜族自治州', '', 1, 1, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('辽宁', '沈阳,大连,鞍山,抚顺,本溪,丹东,锦州,营口,阜新,辽阳,盘锦,铁岭,朝阳,葫芦岛', '', 1, 1, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('陕西', '西安,铜川,宝鸡,咸阳,渭南,延安,汉中,榆林,安康,商洛', '', 1, 1, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('新疆', '乌鲁木齐,克拉玛依,吐鲁番,哈密,昌吉回族自治州,博尔塔拉蒙古自治州,巴音郭楞蒙古自治州,克孜勒苏柯尔克孜自治州,阿克苏地区,喀什地区,和田地区,伊犁哈萨克自治州,塔城地区,阿勒泰地区,石河子,阿拉尔,图木舒克,五家渠', '', 1, 1, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('宁夏', '银川,石嘴山,吴忠,固原,中卫', '', 1, 1, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('青海', '西宁,海东,海北藏族自治州,黄南藏族自治州,海南藏族自治州,果洛藏族自治州,玉树藏族自治州,海西蒙古族藏族自治州', '', 1, 1, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('山西', '太原,大同,阳泉,长治,晋城,朔州,晋中,运城,忻州,临汾,吕梁', '', 1, 1, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('河北', '石家庄,唐山,秦皇岛,邯郸,邢台,保定,张家口,承德,沧州,廊坊,衡水', '', 1, 1, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('河南', '郑州,开封,洛阳,平顶山,安阳,鹤壁,新乡,焦作,濮阳,许昌,漯河,三门峡,南阳,商丘,周口,驻马店,济源示范区', '', 1, 1, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('甘肃', '兰州,嘉峪关,金昌,白银,天水,武威,张掖,平凉,酒泉,庆阳,定西,陇南,临夏回族自治州,甘南藏族自治州', '', 1, 1, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('山东', '青岛,淄博,枣庄,东营,烟台,潍坊,济宁,泰安,威海,日照,莱州,临沂,聊城,滨州,菏泽', '', 1, 1, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('内蒙古', '呼和浩特,包头,乌海,赤峰,通辽,鄂尔多斯,呼伦贝尔,巴彦淖尔,乌兰察布,兴安盟,锡林郭勒盟,阿拉善盟', '', 1, 1, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('浙江', '杭州,宁波,温州,嘉兴,湖州,绍兴,金华,衢州,舟山,台州,丽水', '', 1, 1, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('福建', '福州,厦门,莆田,三明,泉州,漳州,南平,龙岩,宁德', '', 1, 1, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('安徽', '合肥,芜湖,蚌埠,淮南,马鞍山,淮北,铜陵,安庆,黄山,滁州,阜阳,宿州,六安,亳州,池州,宣城', '', 1, 1, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('湖北', '武汉,黄石,十堰,宜昌,襄阳,鄂州,荆门,孝感,荆州,黄冈,咸宁,随州,恩施土家族苗族自治州,仙桃,潜江,天门,神农架林区', '', 1, 1, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('湖南', '长沙,株洲,湘潭,衡阳,邵阳,岳阳,常德,张家界,益阳,郴州,永州,怀化,娄底,湘西土家族苗族自治州', '', 1, 1, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('江西', '南昌,景德镇,萍乡,九江,新余,鹰潭,赣州,吉安,宜春,抚州,上饶', '', 1, 1, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('贵州', '贵阳,六盘水,遵义,安顺,毕节,铜仁,黔东南苗族侗族自治州,黔南布依族苗族自治州,黔西南布依族苗族自治州', '', 1, 1, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('广西', '南宁,柳州,桂林,梧州,北海,防城港,钦州,贵港,玉林,百色,贺州,河池,来宾,崇左', '', 1, 1, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('海南', '海口,三亚,三沙,儋州,琼海,文昌,万宁,东方,澄迈,定安,屯昌,临高,白沙黎族自治县,昌江黎族自治县,乐东黎族自治县,陵水黎族自治县,保亭黎族苗族自治县,琼中黎族苗族自治县', '', 1, 1, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('广东', '珠海,汕头,佛山,韶关,湛江,肇庆,江门,茂名,惠州,梅州,汕尾,河源,阳江,清远,东莞,潮州,揭阳,云浮', '', 1, 1, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('西藏', '拉萨,日喀则,昌都,林芝,山南,那曲,阿里地区', '', 1, 1, NULL);
+INSERT INTO `t_area_email` (`name`, `area`, `email`, `is_virtual`, `is_active`, `remark`) VALUES ('云南', '昆明,曲靖,玉溪,保山,昭通,丽江,普洱,临沧,红河哈尼族彝族自治州,文山壮族苗族自治州,西双版纳傣族自治州,大理白族自治州,德宏傣族景颇族自治州,怒江傈僳族自治州,迪庆藏族自治州', '', 1, 1, NULL);
+
 
 
 -- ----------------------------