python安装winservice

  1 import os
  2 import sys
  3 import json
  4 import logging
  5 import win32serviceutil
  6 import win32service
  7 import win32event
  8 import servicemanager
  9 import time
 10 import threading
 11 # import platform
 12 import struct
 13 import datetime
 14 import math
 15 import mysql.connector
 16 import pyodbc
 17 import sqlite3
 18 import traceback
 19 # import pymssql
 20 # import psycopg2
 21 from watchdog.observers import Observer
 22 from watchdog.events import FileSystemEventHandler
 23 
 24 
 25 class ConfigChangeHandler(FileSystemEventHandler):
 26     """配置文件变更处理器"""
 27 
 28     def __init__(self, callback, logger):
 29         super().__init__()
 30         self.callback = callback
 31         self.logger = logger
 32 
 33     def on_modified(self, event):
 34         if not event.is_directory and event.src_path.endswith('.json'):
 35             self.logger.info(f"检测到配置文件变更: {event.src_path}")
 36             self.callback()
 37 
 38 
 39 class DatabaseService(win32serviceutil.ServiceFramework):
 40     _svc_name_ = "EnhancedDatabaseService"
 41     _svc_display_name_ = "Enhanced Database Service"
 42     _svc_description_ = "支持多数据库查询、结果导出至MySql数据库和热重载配置的服务"
 43 
 44     def __init__(self, args):
 45         win32serviceutil.ServiceFramework.__init__(self, args)
 46         self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
 47         self.config = {}
 48         self.output_db_config = {}
 49         self.config_path = "C:\\ServiceConfig\\db_config.json"
 50         self.logger = None
 51         self.last_timestamp = None
 52         self.last_datetime = None
 53         self.last_date = None
 54         self.timestamp_file = "C:\\ServiceData\\last_timestamp.txt"
 55         self.last_config_mtime = 0
 56         self.observer = None
 57         self.config_lock = threading.Lock()
 58         self.output_conn = None  # 输出数据库连接
 59 
 60     def _setup_logger(self):
 61         """配置日志记录器"""
 62         try:
 63             # 确保目录存在
 64             os.makedirs("C:\\ServiceData", exist_ok=True)
 65 
 66             logger = logging.getLogger(self._svc_name_)
 67             logger.setLevel(logging.INFO)
 68 
 69             # 使用绝对路径
 70             handler = logging.FileHandler("C:\\ServiceData\\service_log.log")
 71             formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
 72             handler.setFormatter(formatter)
 73             logger.addHandler(handler)
 74             return logger
 75         except Exception as e:
 76             # 如果日志不可用,尝试基础日志
 77             logging.basicConfig(level=logging.INFO,
 78                                 filename="C:\\ServiceData\\service_fallback.log",
 79                                 format='%(asctime)s - %(levelname)s - %(message)s')
 80             logger = logging.getLogger(self._svc_name_)
 81             logger.error(f"无法创建日志文件: {str(e)}")
 82             return logger
 83 
 84     def SvcStop(self):
 85         """停止服务"""
 86         self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
 87         self.logger.info("接收到停止请求")
 88         if self.observer:
 89             self.observer.stop()
 90         # 关闭输出数据库连接
 91         if self.output_conn and self.output_conn.is_connected():
 92             self.output_conn.close()
 93             self.logger.info("已关闭输出数据库连接")
 94         win32event.SetEvent(self.hWaitStop)
 95 
 96     def SvcDoRun(self):
 97         """服务主运行逻辑"""
 98         try:
 99             # 确保目录存在
100             os.makedirs("C:\\ServiceData", exist_ok=True)
101             os.makedirs("C:\\ServiceConfig", exist_ok=True)
102 
103             # 初始化日志
104             self.logger = self._setup_logger()
105             self.logger.info("===== 服务启动开始 =====")
106             self.logger.info(f"系统架构: {self.get_system_architecture()}")
107 
108             # 通知服务管理器服务已启动
109             servicemanager.LogMsg(
110                 servicemanager.EVENTLOG_INFORMATION_TYPE,
111                 servicemanager.PYS_SERVICE_STARTED,
112                 (self._svc_name_, '')
113             )
114 
115             # 加载配置和时间戳
116             self.logger.info("加载配置...")
117             self._load_config()
118             self.logger.info("配置文件验证通过")
119             self.logger.info("初始化输出数据库连接...")
120             self._init_output_db()
121             self.logger.info("输出数据库连接成功")
122             self.logger.info("加载时间戳...")
123             self._load_timestamp()
124 
125             # 设置配置文件监控
126             self._setup_config_watcher()
127 
128             # 进入主循环
129             self.logger.info("进入主循环...")
130             self._main_loop()
131 
132         except Exception as e:
133             error_msg = f"服务启动失败: {str(e)}\n{traceback.format_exc()}"
134             if hasattr(self, 'logger') and self.logger:
135                 self.logger.error(error_msg)
136             else:
137                 # 如果日志不可用,尝试写入文件
138                 with open("C:\\ServiceData\\service_error.log", "a") as f:
139                     f.write(f"{datetime.datetime.now()}: {error_msg}\n")
140 
141             # 记录服务启动失败
142             servicemanager.LogMsg(
143                 servicemanager.EVENTLOG_ERROR_TYPE,
144                 0,  # 自定义错误代码
145                 (f"服务 {self._svc_name_} 启动失败: {str(e)}",)
146             )
147             # 确保服务报告失败状态
148             self.ReportServiceStatus(win32service.SERVICE_STOPPED)
149 
150     def _init_output_db(self):
151         """初始化输出数据库连接"""
152         if not self.output_db_config:
153             raise ValueError("配置文件中缺少输出数据库配置")
154 
155         db_type = self.output_db_config.get("database_type", "mysql").lower()
156         if db_type != "mysql":
157             raise ValueError(f"输出数据库只支持MySQL,当前配置为: {db_type}")
158 
159         try:
160             self.output_conn = mysql.connector.connect(
161                 host=self.output_db_config.get("host"),
162                 port=self.output_db_config.get("port", 3306),
163                 user=self.output_db_config.get("username"),
164                 password=self.output_db_config.get("password"),
165                 database=self.output_db_config.get("database")
166             )
167             self.logger.info(
168                 f"成功连接到输出数据库: {self.output_db_config.get('host')}/{self.output_db_config.get('database')}")
169         except Exception as e:
170             self.logger.error(f"输出数据库连接失败: {str(e)}")
171             raise
172 
173     def _setup_config_watcher(self):
174         """设置配置文件监控"""
175         try:
176             self.observer = Observer()
177             event_handler = ConfigChangeHandler(self._load_config, self.logger)
178             config_dir = os.path.dirname(self.config_path)
179             self.observer.schedule(event_handler, config_dir, recursive=False)
180             self.observer.start()
181             self.logger.info("配置文件监听器已启动")
182         except Exception as e:
183             self.logger.error(f"启动配置文件监听器失败: {str(e)}")
184 
185     def get_system_architecture(self):
186         """返回系统架构信息"""
187         try:
188             # 方法1:使用struct判断位数
189             if struct.calcsize("P") * 8 == 64:
190                 return "x64"
191             return "x86"
192         except:
193             return "unknown"
194 
195     def _load_config(self):
196         """加载或重载配置文件"""
197         try:
198             self.logger.info(f"尝试加载配置文件: {self.config_path}")
199             if not os.path.exists(self.config_path):
200                 raise FileNotFoundError(f"配置文件不存在: {self.config_path}")
201 
202             current_mtime = os.path.getmtime(self.config_path)
203             if self.last_config_mtime and current_mtime <= self.last_config_mtime:
204                 self.logger.info("配置文件未修改,跳过加载")
205                 return
206 
207             with self.config_lock:
208                 with open(self.config_path, 'r', encoding='utf-8') as f:
209                     new_config = json.load(f)
210 
211                 # 验证配置结构
212                 required_keys = ['output_database', 'database_list']
213                 for key in required_keys:
214                     if key not in new_config:
215                         raise ValueError(f"配置文件缺少必要字段: {key}")
216 
217                 # 验证输出数据库配置
218                 output_keys = ['host', 'username', 'password', 'database']
219                 for key in output_keys:
220                     if key not in new_config['output_database']:
221                         raise ValueError(f"输出数据库配置缺少必要字段: {key}")
222 
223                 # 验证数据库列表
224                 if not isinstance(new_config['database_list'], list) or len(new_config['database_list']) == 0:
225                     raise ValueError("database_list 必须是非空列表")
226 
227                 self.config = new_config
228                 self.output_db_config = new_config['output_database']
229                 self.last_config_mtime = current_mtime
230                 self.logger.info(f"配置重新加载成功,共 {len(self.config['database_list'])} 个数据库配置")
231         except Exception as e:
232             self.logger.error(f"加载配置失败: {str(e)}")
233             # 保留旧的配置继续运行
234             if not self.config:
235                 raise
236 
237     def _load_timestamp(self):
238         """加载或创建时间戳文件"""
239         try:
240             if os.path.exists(self.timestamp_file):
241                 with open(self.timestamp_file, 'r') as f:
242                     self.last_timestamp = float(f.read().strip())
243                     self.last_datetime = datetime.datetime.fromtimestamp(self.last_timestamp).strftime('%Y-%m-%d %H:%M:%S')
244                     self.last_date = datetime.datetime.fromtimestamp(self.last_timestamp).strftime('%Y-%m-%d')
245                 self.logger.info(f"加载历史时间戳: {datetime.datetime.fromtimestamp(self.last_timestamp)}")
246             else:
247                 self.last_timestamp = time.time()
248                 self.last_datetime = time.strftime('%Y-%m-%d %H:%M:%S')
249                 self.last_date = time.strftime('%Y-%m-%d')
250                 with open(self.timestamp_file, 'w') as f:
251                     f.write(str(self.last_timestamp))
252                 self.logger.info(f"创建新时间戳: {datetime.datetime.fromtimestamp(self.last_timestamp)}")
253         except Exception as e:
254             self.logger.error(f"时间戳处理失败: {str(e)}")
255             self.last_timestamp = time.time()
256             self.last_datetime = time.strftime('%Y-%m-%d %H:%M:%S')
257             self.last_date = time.strftime('%Y-%m-%d')
258 
259     def _get_access_driver(self):
260         """根据系统位数返回合适的Access驱动名称"""
261         # 判断系统位数
262         is_64bit = self.get_system_architecture() == "x64"
263 
264         # 常见Access驱动名称
265         drivers = [
266             "Microsoft Access Driver (*.mdb, *.accdb)",
267             "Microsoft Access Driver (*.mdb)",
268             "Microsoft Access-Treiber (*.mdb, *.accdb)"  # 德语系统
269         ]
270 
271         # 尝试检测已安装的驱动
272         for driver in drivers:
273             try:
274                 test_db = "C:\\Windows\\Temp\\test_access_connection.accdb"
275                 conn = pyodbc.connect(f"DRIVER={{{driver}}};DBQ={test_db};")
276                 conn.close()
277                 self.logger.info(f"检测到可用Access驱动: {driver}")
278                 return driver
279             except:
280                 continue
281 
282         # 如果都失败,返回默认驱动
283         default_driver = "Microsoft Access Driver (*.mdb, *.accdb)"
284         self.logger.warning(f"无法检测Access驱动,使用默认驱动: {default_driver}")
285         return default_driver
286 
287     def _get_db_connection(self, config, db_type=None):
288         """获取数据库连接"""
289         if not db_type:
290             db_type = config.get("database_type", "").lower()
291 
292         try:
293             if db_type == "mysql":
294                 return mysql.connector.connect(
295                     host=config.get("host"),
296                     port=config.get("port", 3306),
297                     user=config.get("username"),
298                     password=config.get("password"),
299                     database=config.get("database")
300                 )
301             elif db_type == "access":
302                 # 获取合适的Access驱动
303                 driver = config.get("driver") or self._get_access_driver()
304 
305                 # 构建连接字符串
306                 db_path = config.get("db_path")
307                 if not os.path.exists(db_path):
308                     raise FileNotFoundError(f"Access数据库文件不存在: {db_path}")
309 
310                 conn_str = (
311                     f'DRIVER={{{driver}}};'
312                     f'DBQ={db_path};'
313                 )
314 
315                 # 添加密码(如果配置了)
316                 if config.get("db_pwd"):
317                     conn_str += f'PWD={config.get("db_pwd")};'
318 
319                 # 32位系统可能需要额外的参数
320                 if self.get_system_architecture() == "x86":
321                     conn_str += 'ExtendedAnsiSQL=1;'
322 
323                 self.logger.info(f"使用Access连接字符串: {conn_str}")
324                 return pyodbc.connect(conn_str)
325             # elif db_type in ["sqlserver", "mssql"]:
326             #     return pymssql.connect(
327             #         server=config.get("host"),
328             #         port=config.get("port", 1433),
329             #         user=config.get("username"),
330             #         password=config.get("password"),
331             #         database=config.get("database")
332             #     )
333             # elif db_type in ["postgresql", "postgres"]:
334             #     return psycopg2.connect(
335             #         host=config.get("host"),
336             #         port=config.get("port", 5432),
337             #         user=config.get("username"),
338             #         password=config.get("password"),
339             #         dbname=config.get("database")
340             #     )
341             elif db_type == "sqlite":
342                 return sqlite3.connect(config.get("db_path"))
343             else:
344                 raise ValueError(f"不支持的数据库类型: {db_type}")
345         except Exception as e:
346             self.logger.error(f"数据库连接失败({db_type}): {str(e)}")
347             raise
348 
349     def _write_to_output_db(self, table_name, data, columns):
350         """将数据写入输出数据库,自动创建或更新表结构"""
351         if not data:
352             self.logger.info(f"表 {table_name} 无新数据写入")
353             return 0
354 
355         try:
356             cursor = self.output_conn.cursor()
357             # 确保没有未读结果
358             self._consume_unread_results(cursor)
359 
360             # 检查表是否存在
361             table_exists = True
362             try:
363                 cursor.execute(f"SELECT 1 FROM `{table_name}` LIMIT 0")
364                 # 消费结果
365                 cursor.fetchall()
366             except Exception:
367                 table_exists = False
368 
369             # 如果表不存在,创建新表
370             if not table_exists:
371                 # 创建表的列定义
372                 column_definitions = ["auto_id INT AUTO_INCREMENT PRIMARY KEY"]
373 
374                 # 添加数据列(全部为TEXT类型)
375                 for idx, col in enumerate(columns):
376                     # 避免保留列名冲突
377                     col_name = col
378                     if col.lower() in ["auto_id", "import_timestamp"]:
379                         col_name = f"src_{col}"
380                         self.logger.warning(f"列名 '{col}' 与保留名冲突,重命名为 '{col_name}'")
381 
382                     column_definitions.append(f"`{col_name}` TEXT")
383 
384                 # 添加时间戳列
385                 column_definitions.append("import_timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
386 
387                 # 创建表
388                 create_table_sql = f"""
389                 CREATE TABLE `{table_name}` (
390                     {', '.join(column_definitions)}
391                 )
392                 """
393                 cursor.execute(create_table_sql)
394                 self.logger.info(f"创建新表: {table_name} 包含 {len(columns)} 列")
395 
396                 # 更新列映射
397                 columns = [col_name.replace("src_", "") if "src_" in col_name else col_name
398                            for col_name in columns]
399 
400             # 检查并添加缺失列
401             if table_exists:
402                 # 获取现有列
403                 cursor.execute(f"SHOW COLUMNS FROM `{table_name}`")
404                 existing_columns = [col[0].lower() for col in cursor.fetchall()]
405 
406                 # 添加缺失列
407                 for col in columns:
408                     col_lower = col.lower()
409                     # 避免保留列名冲突
410                     if col_lower in ["auto_id", "import_timestamp"]:
411                         col = f"src_{col}"
412                         col_lower = col.lower()
413 
414                     # 跳过已存在列
415                     if col_lower in existing_columns:
416                         continue
417 
418                     # 添加新列
419                     add_col_sql = f"ALTER TABLE `{table_name}` ADD COLUMN `{col}` TEXT"
420                     try:
421                         cursor.execute(add_col_sql)
422                         self.logger.info(f"添加新列: {table_name}.{col}")
423                     except Exception as e:
424                         self.logger.warning(f"添加列失败 {col}: {str(e)}")
425 
426             # 准备插入数据
427             insert_columns = []
428             for col in columns:
429                 # 处理列名冲突
430                 if col.lower() in ["auto_id", "import_timestamp"]:
431                     col = f"src_{col}"
432                 insert_columns.append(f"`{col}`")
433 
434             # 构建插入SQL
435             placeholders = ', '.join(['%s'] * len(insert_columns))
436             insert_sql = f"""
437             INSERT INTO `{table_name}` ({', '.join(insert_columns)}) 
438             VALUES ({placeholders})
439             """
440 
441             # 执行批量插入
442             batch_size = 1000
443             inserted_count = 0
444             total_rows = len(data)
445 
446             for i in range(0, total_rows, batch_size):
447                 batch = data[i:i + batch_size]
448                 try:
449                     # 转换为MySQL可接受的格式
450                     converted_batch = []
451                     for row in batch:
452                         converted_row = []
453                         for value in row:
454                             # 空值处理
455                             if value is None:
456                                 converted_row.append(None)
457                             # 处理datetime对象
458                             elif isinstance(value, datetime.datetime):
459                                 converted_row.append(value.strftime("%Y-%m-%d %H:%M:%S"))
460                             # 处理日期对象
461                             elif isinstance(value, datetime.date):
462                                 converted_row.append(value.strftime("%Y-%m-%d"))
463                             # 处理时间对象
464                             elif isinstance(value, datetime.time):
465                                 converted_row.append(value.strftime("%H:%M:%S"))
466                             # 处理布尔值
467                             elif isinstance(value, bool):
468                                 converted_row.append(1 if value else 0)
469                             # 处理浮点数NaN
470                             elif isinstance(value, float) and math.isnan(value):
471                                 converted_row.append(None)
472                             # 处理其他类型
473                             else:
474                                 converted_row.append(str(value) if not isinstance(value, (int, float)) else value)
475                         converted_batch.append(tuple(converted_row))
476 
477                     cursor.executemany(insert_sql, converted_batch)
478                     inserted_count += len(converted_batch)
479                     self.logger.debug(f"插入批次: {i}-{i + len(converted_batch)}/{total_rows}")
480                 except Exception as e:
481                     self.logger.error(f"批量插入失败: {str(e)}")
482                     # 尝试逐行插入以确定问题行
483                     for j, row in enumerate(converted_batch if 'converted_batch' in locals() else batch):
484                         try:
485                             cursor.execute(insert_sql, row)
486                             inserted_count += 1
487                         except Exception as row_error:
488                             self.logger.error(f"行插入失败 (行 {i + j}): {str(row_error)}")
489                             # 记录问题行但不包含敏感数据
490                             self.logger.debug(f"问题行索引: {i + j}, 数据类型: {[type(v) for v in row]}")
491 
492             self.output_conn.commit()
493             self.logger.info(f"成功写入 {inserted_count} 条数据到表 {table_name}")
494             return inserted_count
495 
496         except Exception as e:
497             self.logger.error(f"写入输出数据库失败: {str(e)}\n{traceback.format_exc()}")
498             # 发生错误时回滚
499             try:
500                 self.output_conn.rollback()
501             except:
502                 pass
503             return 0
504         finally:
505             # 确保关闭游标
506             if cursor:
507                 cursor.close()
508 
509     def _consume_unread_results(self, cursor):
510         try:
511             count = 0
512             while cursor.nextset():
513                 count += 1
514             if count > 0:
515                 self.logger.debug(f"消费了 {count} 个未读结果集")
516         except Exception as e:
517             self.logger.warning(f"清理未读结果时出错: {str(e)}")
518 
519 
520     def _execute_query(self, db_config, query_name, query_info):
521         """执行单个查询并处理结果"""
522         conn = None
523         try:
524             sql_template = query_info.get("sql", "")
525             if not sql_template:
526                 self.logger.warning(f"查询[{query_name}]的SQL为空,跳过")
527                 return False
528 
529             # 替换时间戳占位符
530             sql = sql_template.replace("{last_timestamp}", str(self.last_timestamp))
531             sql = sql.replace("{last_datetime}", str(self.last_datetime))
532             sql = sql.replace("{last_date}", str(self.last_date))
533 
534             # 获取目标表名
535             target_table = query_info.get("target_table", query_name)
536 
537             # 获取数据库连接
538             conn = self._get_db_connection(db_config)
539             cursor = conn.cursor()
540 
541             # 执行查询
542             self.logger.info(f"正在执行查询: {query_name}")
543             cursor.execute(sql)
544             results = cursor.fetchall()
545 
546             if not results:
547                 self.logger.info(f"查询[{query_name}]返回0条记录")
548                 return True
549 
550             # 获取列名
551             columns = [col[0].strip() for col in cursor.description]
552             columns = [col if col else f"col_{idx}" for idx, col in enumerate(columns)]
553 
554             # 写入输出数据库
555             inserted_count = self._write_to_output_db(target_table, results, columns)
556             self.logger.info(f"查询[{query_name}]处理完成,插入{inserted_count}条数据")
557             return inserted_count > 0
558         except Exception as e:
559             self.logger.error(f"查询执行失败 [{query_name}]: {str(e)}\n{traceback.format_exc()}")
560             return False
561         finally:
562             if conn:
563                 conn.close()
564 
565     def _execute_and_update(self):
566         """执行所有查询并更新时间戳"""
567         if not self.config:
568             self.logger.warning("配置未加载,跳过执行")
569             return False
570 
571         database_list = self.config.get("database_list", [])
572         if not database_list:
573             self.logger.warning("未配置任何数据库")
574             return False
575 
576         success = True
577         total_inserted = 0
578         total_queries = 0
579         successful_queries = 0
580 
581         # 遍历每个数据库配置
582         for db_index, db_config in enumerate(database_list):
583             db_type = db_config.get("database_type", "unknown")
584             db_name = db_config.get("database", f"DB_{db_index}")
585             self.logger.info(f"开始处理数据库 #{db_index + 1} ({db_type}/{db_name})")
586 
587             queries = db_config.get("queries", {})
588             if not queries:
589                 self.logger.info(f"数据库 #{db_index + 1} 未配置查询,跳过")
590                 continue
591 
592             # 执行该数据库的所有查询
593             for name, info in queries.items():
594                 total_queries += 1
595                 if self._execute_query(db_config, name, info):
596                     successful_queries += 1
597                 else:
598                     success = False
599                     self.logger.warning(f"查询[{name}]执行失败,继续处理其他查询")
600 
601         # 统计结果
602         self.logger.info(f"查询执行完成: {successful_queries}/{total_queries} 个查询成功")
603 
604         # 仅当所有查询成功时才更新时间戳
605         if success and total_queries > 0:
606             new_timestamp = time.time()
607             with open(self.timestamp_file, 'w') as f:
608                 f.write(str(new_timestamp))
609             self.last_timestamp = new_timestamp
610             self.logger.info(f"更新时间戳: {datetime.datetime.fromtimestamp(new_timestamp)}")
611             return True
612 
613         self.logger.warning("部分查询失败,跳过更新时间戳")
614         return False
615 
616     def _main_loop(self):
617         """服务主循环"""
618         self.logger.info("服务主循环开始")
619 
620         # 报告服务正在运行
621         self.ReportServiceStatus(win32service.SERVICE_RUNNING)
622 
623         while True:
624             # 检查停止事件
625             stop_event = win32event.WaitForSingleObject(self.hWaitStop, 5000)
626             if stop_event == win32event.WAIT_OBJECT_0:
627                 self.logger.info("服务停止请求接收")
628                 break
629 
630             try:
631                 # 重新加载配置(如果修改过)
632                 self._load_config()
633 
634                 # 如果需要重新初始化输出数据库连接
635                 if self.output_conn and not self.output_conn.is_connected():
636                     self.logger.warning("输出数据库连接断开,尝试重新连接")
637                     self._init_output_db()
638 
639                 # 执行查询并同步数据
640                 self.logger.info("开始执行数据同步...")
641                 sync_success = self._execute_and_update()
642 
643                 if sync_success:
644                     self.logger.info("数据同步成功完成")
645                 else:
646                     self.logger.warning("数据同步过程中出现错误")
647             except Exception as e:
648                 self.logger.error(f"主循环错误: {str(e)}\n{traceback.format_exc()}")
649 
650             # 按配置间隔等待
651             interval = self.config.get("interval_seconds", 60) if self.config else 60
652             self.logger.info(f"等待 {interval} 秒后进行下一次同步")
653             time.sleep(interval)
654 
655         self.logger.info("服务主循环结束")
656         # 报告服务已停止
657         self.ReportServiceStatus(win32service.SERVICE_STOPPED)
658 
659 
660 if __name__ == '__main__':
661     # 添加启动日志
662     try:
663         os.makedirs("C:\\ServiceData", exist_ok=True)
664         with open("C:\\ServiceData\\service_bootstrap.log", "a") as f:
665             f.write(f"{datetime.datetime.now()}: 服务启动参数: {sys.argv}\n")
666     except Exception as e:
667         print(f"创建启动日志失败: {str(e)}")
668 
669     if len(sys.argv) == 1:
670         try:
671             servicemanager.Initialize()
672             servicemanager.PrepareToHostSingle(DatabaseService)
673             servicemanager.StartServiceCtrlDispatcher()
674         except Exception as e:
675             with open("C:\\ServiceData\\service_bootstrap.log", "a") as f:
676                 f.write(f"{datetime.datetime.now()}: 服务调度失败: {str(e)}\n{traceback.format_exc()}")
677             raise
678     else:
679         win32serviceutil.HandleCommandLine(DatabaseService)
View Code
 1 {
 2   "version": 2.0,
 3   "interval_seconds": 300,
 4   "database_list": [
 5     {
 6       "database_type": "mysql",
 7       "host": "192.168.1.123",
 8       "username": "username",
 9       "password": "pwd",
10       "database": "mysql_db",
11       "port": 3306,
12       "queries": {
13         "a_table": {
14           "sql": "SELECT * FROM a_table WHERE create_time > {last_timestamp}",
15           "target_table": "mysql_db_a_table"
16         },
17         "b_table": {
18           "sql": "SELECT * FROM b_table WHERE a_id IN (SELECT id FROM a_table WHERE create_time > {last_timestamp})",
19           "target_table": "mysql_db_b_table"
20         },
21         "c_table": {
22           "sql": "SELECT * FROM c_table WHERE a_id IN (SELECT id FROM a_table WHERE create_time > {last_timestamp})",
23           "target_table": "mysql_db_c_table"
24         }
25       }
26     },
27     {
28       "database_type": "access",
29       "db_path": "...filepath\\access.mdb",
30       "db_pwd": "access_pwd",
31       "queries": {
32         "TableA": {
33           "sql": "SELECT * FROM [TableA] WHERE [CreateTime]>CDate('{last_datetime}')",
34           "target_table": "access_TableA"
35         },
36         "TableB": {
37           "sql": "SELECT DISTINCT pi2.* FROM [TableB] as pi2,[TableA] as td WHERE pi2.[QuestDate]=td.[CreateTime] AND pi2.[ID]=td.[SampleID] AND td.[TestTime]>CDate('{last_datetime}')",
38           "target_table": "access_TableB"
39         },
40         "TableC": {
41           "sql": "SELECT * FROM [TableC]",
42           "target_table": "access_TableC"
43         }
44       }
45     }
46   ],
47   "output_database": {
48     "enabled": true,
49     "database_type": "mysql",
50     "host": "127.0.0.1",
51     "username": "username_mysql",
52     "password": "pwd_mysql",
53     "database": "output_db",
54     "port": 3306
55   }
56 }
View Code

 

```markdown
# 本项目主要功能为:同步其它数据库中指定 `queries` 内容至数据库配置 `output_database`,并按 `queries`.`target_table` 生成指定表(如果表存在了则追加内容)

# Windows服务安装与配置指南

## 开发环境要求
- **Python版本**: 3.8
- **操作系统**: Windows 7及以上版本

## 安装Python依赖
```bash
pip install pywin32 mysql-connector-python pyodbc
# 可选数据库驱动(根据需求安装):
pip install pymssql psycopg2
```

## 目录设置
1. 在C盘根目录创建必需文件夹:
```powershell
mkdir C:\ServiceData
mkdir C:\ServiceConfig
```

2. 复制项目文件到配置目录:
```bash
copy db_config.json C:\ServiceConfig\
copy *.py C:\ServiceConfig\
```

## 服务管理命令
| 操作 | 命令 | 说明 |
|------|------|------|
| **安装服务** | `python your_script.py install` | 基本安装 |
| **设置自启动** | `python your_script.py --startup auto install` | 安装时设置自动启动 |
| **启动服务** | `python your_script.py start` ||
| **停止服务** | `python your_script.py stop` ||
| **卸载服务** | `python your_script.py remove` ||
| **调试模式** | `python your_script.py debug` | 用于服务调试 |

## 目录结构说明
```
C:\
├── ServiceData\# 服务运行时数据存储
└── ServiceConfig\# 服务配置文件目录
├── db_config.json # 数据库配置文件
├── your_script.py # 主服务脚本
└── ...# 其他.py文件
```

## 使用注意事项
1. 所有命令需**以管理员身份运行**
2. 首次安装建议使用调试模式测试服务逻辑:
```bash
python your_script.py debug
```
3. 生产环境部署推荐使用自启动参数:
```bash
python your_script.py --startup auto install
```
4. 服务日志查看位置:
**事件查看器 → Windows日志 → 应用程序**
```

 

posted @ 2025-07-28 14:48  动灵  阅读(9)  评论(0)    收藏  举报