From 3532249a6c3afc3d4937136a20f90d5c360ef037 Mon Sep 17 00:00:00 2001
From: liuyebo <1515783401@qq.com>
Date: Mon, 24 Feb 2025 14:35:58 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=97=A5=E5=BF=97=E7=AE=A1?=
=?UTF-8?q?=E7=90=86?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
auto_email/__init__.py | 22 +++++++++++
auto_email/error_email.py | 77 +++++++++++++++++++++++++++++++++++++++
cron.py | 23 ------------
docker-compose.yml | 2 +-
fcb_ai_db_sync.py | 38 +++++++++++++++++++
fcbsync_dbphoto_mysql.py | 39 ++++++++++++--------
log/__init__.py | 70 +++++++++++++++++++++++++++++++++++
requirements.txt | 1 +
8 files changed, 232 insertions(+), 40 deletions(-)
create mode 100644 auto_email/__init__.py
create mode 100644 auto_email/error_email.py
delete mode 100644 cron.py
create mode 100644 fcb_ai_db_sync.py
create mode 100644 log/__init__.py
diff --git a/auto_email/__init__.py b/auto_email/__init__.py
new file mode 100644
index 0000000..2058bfa
--- /dev/null
+++ b/auto_email/__init__.py
@@ -0,0 +1,22 @@
+# 尝试次数
+TRY_TIMES = 3
+# 最小等待时间(秒)
+MIN_WAIT_TIME = 1
+# 最大等待时间(秒)
+MAX_WAIT_TIME = 3
+
+# 程序异常短信配置
+ERROR_EMAIL_CONFIG = {
+ # SMTP服务器地址
+ "smtp_server": "smtp.163.com",
+ # 连接SMTP的端口
+ "port": 994,
+ # 发件人邮箱地址,请确保开启了SMTP邮件服务!
+ "sender": "EchoLiu618@163.com",
+ # 授权码--用于登录第三方邮件客户端的专用密码,不是邮箱密码
+ "authorization_code": "OKPQLIIVLVGRZYVH",
+ # 收件人邮箱地址
+ "receivers": ["1515783401@qq.com"],
+ # 尝试次数
+ "retry_times": 3,
+}
diff --git a/auto_email/error_email.py b/auto_email/error_email.py
new file mode 100644
index 0000000..16dd93b
--- /dev/null
+++ b/auto_email/error_email.py
@@ -0,0 +1,77 @@
+import datetime
+import logging
+import smtplib
+from email.mime.text import MIMEText
+
+from tenacity import retry, stop_after_attempt, wait_random
+
+from auto_email import ERROR_EMAIL_CONFIG, TRY_TIMES, MIN_WAIT_TIME, MAX_WAIT_TIME
+from log import HOSTNAME
+
+
+@retry(stop=stop_after_attempt(TRY_TIMES), wait=wait_random(MIN_WAIT_TIME, MAX_WAIT_TIME), reraise=True,
+ after=lambda x: logging.warning("发送邮件失败!"))
+def send_email(email_config, massage):
+ smtp_server = email_config["smtp_server"]
+ port = email_config["port"]
+ sender = email_config["sender"]
+ authorization_code = email_config["authorization_code"]
+ receivers = email_config["receivers"]
+ mail = smtplib.SMTP_SSL(smtp_server, port) # 连接SMTP服务
+ mail.login(sender, authorization_code) # 登录到SMTP服务
+ mail.sendmail(sender, receivers, massage.as_string()) # 发送邮件
+ mail.quit()
+ logging.info(f"成功发送了一封邮件到[{','.join(receivers)}]")
+
+
+def send_error_email(program_name, error_name, error_detail):
+ """
+ 程序出错时发送邮件提醒
+ :param program_name: 运行的程序名
+ :param error_name: 错误名
+ :param error_detail: 错误的详细信息
+ :return:
+ """
+
+ # SMTP 服务器配置
+ sender = ERROR_EMAIL_CONFIG["sender"]
+ receivers = ERROR_EMAIL_CONFIG["receivers"]
+
+ # 获取程序出错的时间
+ error_time = datetime.datetime.strftime(datetime.datetime.today(), "%Y-%m-%d %H:%M:%S:%f")
+ # 邮件内容
+ subject = f"【程序异常提醒】{program_name}({HOSTNAME}) {error_time}" # 邮件的标题
+ content = f'''
+
+
程序运行异常通知
+
+
程序:【{program_name}】运行过程中出现异常错误,下面是具体的异常信息,请及时核查处理!
+
+
+
+ | 程序异常详细信息 |
+
+
+
+
+ | 异常简述 |
+ {error_name} |
+
+
+ | 异常详情 |
+ {error_detail} |
+
+
+
+
+
+
+ ''' # 邮件的正文部分
+ # 实例化一个文本对象
+ massage = MIMEText(content, 'html', 'utf-8')
+ massage['Subject'] = subject # 标题
+ massage['From'] = sender # 发件人
+ receivers_str = ','.join(receivers)
+ massage['To'] = receivers_str # 收件人
+
+ send_email(ERROR_EMAIL_CONFIG, massage)
diff --git a/cron.py b/cron.py
deleted file mode 100644
index 7355f93..0000000
--- a/cron.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from datetime import datetime, timedelta
-from time import sleep, time
-
-import fcbsync_dbphoto_mysql
-
-if __name__ == '__main__':
- while 1:
- if datetime.now().hour >= 8 and datetime.now().hour < 18:
- # 白天间隔10分钟
- sleep_time = 10 * 60
- else:
- # 夜间间隔20分钟
- sleep_time = 20 * 60
-
- # 执行同步
- start_time = time()
- fcbsync_dbphoto_mysql.main()
- end_time = time()
- elapsed_time = end_time - start_time
- if elapsed_time > 30:
- print('警告!执行时间超过30秒,请检查程序运行状态!')
- print(f'下一次同步时间:{datetime.now() + timedelta(seconds=sleep_time)}')
- sleep(sleep_time)
diff --git a/docker-compose.yml b/docker-compose.yml
index e809ae1..8d5fe2c 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -6,4 +6,4 @@ services:
context: .
container_name: fcb_ai_db_sync
hostname: fcb_ai_db_sync
- command: ["cron.py"]
\ No newline at end of file
+ command: ["fcb_ai_db_sync.py"]
\ No newline at end of file
diff --git a/fcb_ai_db_sync.py b/fcb_ai_db_sync.py
new file mode 100644
index 0000000..80386d6
--- /dev/null
+++ b/fcb_ai_db_sync.py
@@ -0,0 +1,38 @@
+import logging.config
+import traceback
+from datetime import datetime, timedelta
+from time import sleep, time
+
+import fcbsync_dbphoto_mysql
+from auto_email.error_email import send_error_email
+from log import LOGGING_CONFIG
+
+if __name__ == '__main__':
+ program_name = 'AI赋能数据库同步脚本'
+ logging.config.dictConfig(LOGGING_CONFIG)
+
+ try:
+ logging.info(f"【{program_name}】开始运行")
+ while 1:
+ if 8 <= datetime.now().hour < 18:
+ # 白天间隔10分钟
+ sleep_time = 10 * 60
+ else:
+ # 夜间间隔20分钟
+ sleep_time = 20 * 60
+
+ # 执行同步
+ start_time = time()
+ fcbsync_dbphoto_mysql.main()
+ end_time = time()
+ elapsed_time = end_time - start_time
+ if elapsed_time > 30:
+ error_message = f'警告!同步脚本执行时间高达{elapsed_time}秒,请检查程序运行状态!'
+ logging.error(error_message)
+ send_error_email(program_name, '超时警告', error_message)
+ logging.info(f'下一次同步时间:{datetime.now() + timedelta(seconds=sleep_time)}')
+ sleep(sleep_time)
+ except Exception as e:
+ error_logger = logging.getLogger("error")
+ error_logger.error(traceback.format_exc())
+ send_error_email(program_name, repr(e), traceback.format_exc())
diff --git a/fcbsync_dbphoto_mysql.py b/fcbsync_dbphoto_mysql.py
index 478e983..68bd457 100644
--- a/fcbsync_dbphoto_mysql.py
+++ b/fcbsync_dbphoto_mysql.py
@@ -2,11 +2,14 @@
功能:将mysql从源数据表同步目标数据表 主要用于ocr 识别及自动脱敏程序
"""
import configparser
+import logging
#import sys
import os
import platform
import re
import time
+from datetime import datetime
+
# from tkinter import EXCEPTION
import requests
import json
@@ -149,14 +152,14 @@ def sync_dbtarget(source_cursor,source_conn,target_cursor,target_db,i_sync_fiel
target_db.commit()
except Exception as e:
target_db.rollback()
- print('插入失败imp_'+i_table_name+",原因"+str(e))
+ logging.error('插入失败imp_'+i_table_name+",原因"+str(e))
continue
sql = ""
j = j + 1
# 每一千条打印
if divmod(j, 1000)[1] == 0:
- print("已经插入"+i_imp_table_name+":" +str(j) + "条")
- print("插入"+i_imp_table_name+":" + str(j - 1) + "条")
+ logging.info("已经插入"+i_imp_table_name+":" +str(j) + "条")
+ logging.info("插入"+i_imp_table_name+":" + str(j - 1) + "条")
if v_pk_phhd[1:] != '':
v_sql ='';
v_mess='';
@@ -182,10 +185,10 @@ def sync_dbtarget(source_cursor,source_conn,target_cursor,target_db,i_sync_fiel
try:
source_cursor.execute(v_sql)
source_conn.commit()
- print(v_mess+'成功!'+' from:'+i_table_name+' ,to:'+i_imp_table_name)
+ logging.info(v_mess+'成功!'+' from:'+i_table_name+' ,to:'+i_imp_table_name)
except Exception as e:
source_conn.rollback()
- print(v_mess+"失败,原因"+str(e))
+ logging.error(v_mess+"失败,原因"+str(e))
def main():
# path = os.path.abspath(os.path.dirname(__file__))
@@ -297,7 +300,7 @@ def main():
target_conn.commit
except Exception as e:
target_conn.rollback()
- print("删除目标表失败"+"imp_开头表,原因"+str(e))
+ logging.error("删除目标表失败"+"imp_开头表,原因"+str(e))
source_cursor = source_conn.cursor()
try:
@@ -312,7 +315,7 @@ def main():
source_conn.commit
except Exception as e:
source_conn.rollback()
- print("删除源表失败"+"imp_开头表,原因"+str(e))
+ logging.error("删除源表失败"+"imp_开头表,原因"+str(e))
#v_sql ="select date_format(date_sub(max(sync_date),interval 1 day) , '%Y-%m-%d') pk_maxvalue from wzx2017.sys_sybnc_date"
@@ -350,14 +353,14 @@ def main():
v_title = "花费{mtime:.0f}分{stime:.0f}秒时间,同步成功!".format(
mtime=time_elapsed // 60,
stime=time_elapsed % 60)
- print(v_title)
+ logging.info(v_title)
v_sql = 'insert into sys_sync_date(sync_date,sync_direct,depiction) values(CURRENT_TIMESTAMP,"1","{}") '.format(v_title)
try:
target_cursor.execute(v_sql)
target_conn.commit()
except Exception as e:
- print('追加记录至sys_sybnc_date失败?' + ",原因" + str(e))
+ logging.error('追加记录至sys_sybnc_date失败?' + ",原因" + str(e))
target_conn.rollback()
v_sql = 'call pro_sync_waitfor_ocrdata(1,@dd);'
@@ -365,7 +368,7 @@ def main():
target_cursor.execute(v_sql)
target_conn.commit()
except Exception as e:
- print('同步至待ocr 识别完成失败(pro_sync_waitfor_ocrdata)?' + ",原因" + str(e))
+ logging.error('同步至待ocr 识别完成失败(pro_sync_waitfor_ocrdata)?' + ",原因" + str(e))
target_conn.rollback()
v_sql = 'select * from sys_sync_tables where sync_direct="2" and del_flag=0 order by 1 '
@@ -393,14 +396,14 @@ def main():
v_title = "花费{mtime:.0f}分{stime:.0f}秒时间,同步成功!".format(
mtime=time_elapsed // 60,
stime=time_elapsed % 60)
- print(v_title)
+ logging.info(v_title)
v_sql = 'insert into sys_sync_date(sync_date,sync_direct,depiction) values(CURRENT_TIMESTAMP,"2","{}") '.format(v_title)
try:
target_cursor.execute(v_sql)
target_conn.commit()
except Exception as e:
- print('追加记录至sys_sybnc_date失败?' + ",原因" + str(e))
+ logging.error('追加记录至sys_sybnc_date失败?' + ",原因" + str(e))
target_conn.rollback()
v_sql = 'call pro_photoocr_update(1,@dd);'
@@ -408,7 +411,7 @@ def main():
source_cursor.execute(v_sql)
source_conn.commit()
except Exception as e:
- print('ocr识别结果同步完成失败(pro_photoocr_update)?' + ",原因" + str(e))
+ logging.error('ocr识别结果同步完成失败(pro_photoocr_update)?' + ",原因" + str(e))
source_conn.rollback()
v_sql = "select a.pk_phhd from imp_zx_phapply a left join zx_phhd b on a.pk_phhd=b.pk_phhd where a.cStatus='2' and b.pk_phhd is null;"
@@ -427,7 +430,7 @@ def main():
source_conn.commit()
except Exception as e:
v_error='(存在待ocr识别但没有成功的案子,自动处理失败' + ",原因" + str(e)+')'
- print(v_error)
+ logging.error(v_error)
source_conn.rollback()
v_sql = "select count(1) jl from zx_phhd where paint_flag in ('1','2');"
@@ -456,7 +459,11 @@ def main():
)
# v_head=v_mess+v_head
## 定义上午11点和中午12点的时间对象
- v_hour=time.strftime("%H", time.localtime(time.time()))
- if ('09'<=v_hour<'10' or '17'<=v_hour<'18') or v_paint>300 :
+ now = datetime.now()
+ v_hour = now.hour
+ v_minute = now.minute
+ if ((v_hour == 8 and 30 <= v_minute < 45)
+ or (v_hour == 11 and 0 <= v_minute < 15)
+ or (v_hour == 17 and 0 <= v_minute < 15)) or v_paint>300 :
send_data(html_msg, v_head,v_paint)
# os._exit(0)
\ No newline at end of file
diff --git a/log/__init__.py b/log/__init__.py
new file mode 100644
index 0000000..d2e9472
--- /dev/null
+++ b/log/__init__.py
@@ -0,0 +1,70 @@
+import os
+import socket
+
+# 获取主机名,方便区分容器
+HOSTNAME = socket.gethostname()
+# 检测日志文件的路径是否存在,不存在则创建
+LOG_PATHS = [
+ f"log/{HOSTNAME}/error",
+]
+for path in LOG_PATHS:
+ if not os.path.exists(path):
+ os.makedirs(path)
+
+# 配置字典
+LOGGING_CONFIG = {
+ 'version': 1, # 必需,指定配置格式的版本
+ 'disable_existing_loggers': False, # 是否禁用已经存在的logger实例
+
+ # formatters定义了不同格式的日志样式
+ 'formatters': {
+ 'standard': {
+ 'format': '%(asctime)s [%(levelname)s] %(name)s: %(message)s',
+ 'datefmt': '%Y-%m-%d %H:%M:%S',
+ },
+ },
+
+ # handlers定义了不同类型的日志处理器
+ 'handlers': {
+ 'console': {
+ 'class': 'logging.StreamHandler', # 控制台处理器
+ 'level': 'DEBUG',
+ 'formatter': 'standard',
+ 'stream': 'ext://sys.stdout', # 输出到标准输出,默认编码跟随系统,一般为UTF-8
+ },
+ 'file': {
+ 'class': 'logging.handlers.TimedRotatingFileHandler', # 文件处理器,支持日志滚动
+ 'level': 'INFO',
+ 'formatter': 'standard',
+ 'filename': f'log/{HOSTNAME}/fcb_photo_review.log', # 日志文件路径
+ 'when': 'midnight',
+ 'interval': 1,
+ 'backupCount': 14, # 保留的备份文件数量
+ 'encoding': 'utf-8', # 显式指定文件编码为UTF-8以支持中文
+ },
+ 'error': {
+ 'class': 'logging.handlers.TimedRotatingFileHandler',
+ 'level': 'INFO',
+ 'formatter': 'standard',
+ 'filename': f'log/{HOSTNAME}/error/fcb_photo_review_error.log',
+ 'when': 'midnight',
+ 'interval': 1,
+ 'backupCount': 14,
+ 'encoding': 'utf-8',
+ },
+ },
+
+ # loggers定义了日志记录器
+ 'loggers': {
+ '': { # 根记录器
+ 'handlers': ['console', 'file'], # 关联的处理器
+ 'level': 'DEBUG', # 根记录器的级别
+ 'propagate': False, # 是否向上级传播日志信息
+ },
+ 'error': {
+ 'handlers': ['console', 'file', 'error'],
+ 'level': 'DEBUG',
+ 'propagate': False,
+ },
+ },
+}
diff --git a/requirements.txt b/requirements.txt
index 7367a10..b82e529 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,5 +2,6 @@ pycryptodome==3.21.0
pymysql==1.1.1
requests==2.32.3
sshtunnel==0.4.0
+tenacity==8.5.0
ufile==3.2.9
xlrd==2.0.1
\ No newline at end of file