266 lines
9.1 KiB
Python
266 lines
9.1 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
周报数据库同步工具
|
|
将 Markdown 周报的元数据同步到 MySQL 数据库
|
|
|
|
用法:
|
|
python3 sync_reports_to_db.py --all # 同步所有周报
|
|
python3 sync_reports_to_db.py --file xxx.md # 同步单个文件
|
|
python3 sync_reports_to_db.py --week 2026-W35 # 同步指定周
|
|
"""
|
|
|
|
import os
|
|
import re
|
|
import sys
|
|
import argparse
|
|
import json
|
|
from pathlib import Path
|
|
from datetime import datetime
|
|
|
|
# 数据库配置
|
|
DB_CONFIG = {
|
|
'host': 'localhost',
|
|
'user': 'root',
|
|
'password': '', # 从 .env 读取
|
|
'database': 'resume'
|
|
}
|
|
|
|
REPORTS_DIR = Path(__file__).parent / 'weekly-reports'
|
|
|
|
|
|
def load_env():
|
|
"""读取 .env 文件"""
|
|
env = {}
|
|
env_path = Path(__file__).parent.parent / '.env'
|
|
try:
|
|
with open(env_path, 'r', encoding='utf-8') as f:
|
|
for line in f:
|
|
line = line.strip()
|
|
if not line or line.startswith('#') or '=' not in line:
|
|
continue
|
|
key, _, value = line.partition('=')
|
|
env[key.strip()] = value.strip().strip('"').strip("'")
|
|
except FileNotFoundError:
|
|
pass
|
|
return env
|
|
|
|
|
|
def parse_markdown_report(filepath):
|
|
"""解析 Markdown 周报文件,提取元数据"""
|
|
with open(filepath, 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
|
|
data = {
|
|
'file_path': str(filepath),
|
|
'year': None,
|
|
'week_number': None,
|
|
'start_date': None,
|
|
'end_date': None,
|
|
'project': None,
|
|
'main_task': None,
|
|
'daily_work': {},
|
|
'problems': [],
|
|
'word_count': len(content)
|
|
}
|
|
|
|
# 提取年份和周数(从文件名或内容)
|
|
filename = filepath.name
|
|
match = re.match(r'(\d{4})-W(\d{2})', filename)
|
|
if match:
|
|
data['year'] = int(match.group(1))
|
|
data['week_number'] = int(match.group(2))
|
|
|
|
# 提取日期范围
|
|
date_match = re.search(r'\*\*日期范围:\*\*\s*(\d{4}-\d{2}-\d{2})\s*~\s*(\d{4}-\d{2}-\d{2})', content)
|
|
if date_match:
|
|
data['start_date'] = date_match.group(1)
|
|
data['end_date'] = date_match.group(2)
|
|
|
|
# 提取项目名称
|
|
project_match = re.search(r'项目名称:\s*(维云智造\s*(G\d+)|G\d+)', content)
|
|
if project_match:
|
|
project_text = project_match.group(1)
|
|
g_match = re.search(r'G(\d+)', project_text)
|
|
if g_match:
|
|
data['project'] = f"G{g_match.group(1)}"
|
|
else:
|
|
data['project'] = 'G5' # 默认
|
|
|
|
# 提取主要任务
|
|
task_match = re.search(r'主要任务:\s*(.+?)(?:\n|$)', content)
|
|
if task_match:
|
|
data['main_task'] = task_match.group(1).strip()
|
|
|
|
# 提取每日工作
|
|
daily_section = re.search(r'## 本周工作内容\s*\n([\s\S]*?)(?:\n##|\Z)', content)
|
|
if daily_section:
|
|
daily_text = daily_section.group(1)
|
|
# 匹配每日内容
|
|
days = re.findall(r'###?\s*(?:周 [一二三四五六日]|(\d{4}-\d{2}-\d{2}))\s*\n([\s\S]*?)(?=###?|$)', daily_text)
|
|
for date_str, work_text in days:
|
|
if date_str:
|
|
items = re.findall(r'^\s*[-•*]\s*(.+)$', work_text, re.MULTILINE)
|
|
data['daily_work'][date_str] = items
|
|
|
|
# 提取存在问题
|
|
problem_section = re.search(r'## 存在问题\s*\n([\s\S]*?)(?:\n##|\Z)', content)
|
|
if problem_section:
|
|
problem_text = problem_section.group(1).strip()
|
|
if problem_text and problem_text != '无':
|
|
data['problems'] = [line.strip() for line in problem_text.split('\n') if line.strip()]
|
|
|
|
return data
|
|
|
|
|
|
def sync_to_mysql(report_data, dry_run=False):
|
|
"""同步到 MySQL 数据库"""
|
|
try:
|
|
import mysql.connector
|
|
except ImportError:
|
|
print("❌ 需要安装 mysql-connector-python: pip3 install mysql-connector-python")
|
|
return False
|
|
|
|
env = load_env()
|
|
db_config = {
|
|
'host': 'localhost',
|
|
'user': 'root',
|
|
'password': env.get('MYSQL_ROOT_PASSWORD', ''),
|
|
'database': 'resume',
|
|
'charset': 'utf8mb4'
|
|
}
|
|
|
|
try:
|
|
conn = mysql.connector.connect(**db_config)
|
|
cursor = conn.cursor()
|
|
|
|
# 插入或更新周报元数据
|
|
sql = """
|
|
INSERT INTO weekly_reports
|
|
(year, week_number, start_date, end_date, project, main_task, file_path, has_problems, word_count)
|
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
|
|
ON DUPLICATE KEY UPDATE
|
|
main_task = VALUES(main_task),
|
|
file_path = VALUES(file_path),
|
|
has_problems = VALUES(has_problems),
|
|
word_count = VALUES(word_count),
|
|
updated_at = CURRENT_TIMESTAMP
|
|
"""
|
|
|
|
values = (
|
|
report_data['year'],
|
|
report_data['week_number'],
|
|
report_data['start_date'],
|
|
report_data['end_date'],
|
|
report_data['project'],
|
|
report_data['main_task'],
|
|
report_data['file_path'],
|
|
1 if report_data['problems'] else 0,
|
|
report_data['word_count']
|
|
)
|
|
|
|
if dry_run:
|
|
print(f"📝 [DRY RUN] 会执行:{sql % values}")
|
|
else:
|
|
cursor.execute(sql, values)
|
|
report_id = cursor.lastrowid
|
|
|
|
# 同步每日工作明细
|
|
if report_id:
|
|
for work_date, items in report_data['daily_work'].items():
|
|
day_of_week = datetime.strptime(work_date, '%Y-%m-%d').weekday() + 1
|
|
items_json = json.dumps(items, ensure_ascii=False)
|
|
|
|
daily_sql = """
|
|
INSERT INTO weekly_report_daily
|
|
(report_id, day_of_week, work_date, work_items)
|
|
VALUES (%s, %s, %s, %s)
|
|
ON DUPLICATE KEY UPDATE work_items = VALUES(work_items)
|
|
"""
|
|
|
|
cursor.execute(daily_sql, (report_id, day_of_week, work_date, items_json))
|
|
|
|
# 同步问题记录
|
|
if report_data['problems']:
|
|
for problem in report_data['problems']:
|
|
problem_sql = """
|
|
INSERT INTO weekly_report_problems
|
|
(report_id, problem_description)
|
|
VALUES (%s, %s)
|
|
"""
|
|
cursor.execute(problem_sql, (report_id, problem))
|
|
|
|
conn.commit()
|
|
print(f"✅ 同步成功:{report_data['file_path']}")
|
|
|
|
cursor.close()
|
|
conn.close()
|
|
return True
|
|
|
|
except mysql.connector.Error as err:
|
|
print(f"❌ 数据库错误:{err}")
|
|
return False
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description='周报数据库同步工具')
|
|
group = parser.add_mutually_exclusive_group(required=True)
|
|
group.add_argument('--all', action='store_true', help='同步所有周报')
|
|
group.add_argument('--file', type=str, help='同步单个文件')
|
|
group.add_argument('--week', type=str, help='同步指定周,如 2026-W35')
|
|
group.add_argument('--dry-run', action='store_true', help='预览执行,不实际写入数据库')
|
|
|
|
args = parser.parse_args()
|
|
|
|
files_to_sync = []
|
|
|
|
if args.file:
|
|
files_to_sync = [Path(args.file)]
|
|
elif args.week:
|
|
match = re.match(r'(\d{4})-W(\d{2})', args.week)
|
|
if match:
|
|
year, week = match.groups()
|
|
pattern = f"{year}-W{week}-*.md"
|
|
files_to_sync = list(REPORTS_DIR.glob(f"{year}/{pattern}"))
|
|
elif args.all:
|
|
# 查找所有 Markdown 周报文件
|
|
for year_dir in REPORTS_DIR.iterdir():
|
|
if year_dir.is_dir() and year_dir.name.isdigit():
|
|
files_to_sync.extend(year_dir.glob("*.md"))
|
|
|
|
if not files_to_sync:
|
|
print("⚠️ 未找到需要同步的文件")
|
|
return 1
|
|
|
|
print(f"📂 找到 {len(files_to_sync)} 个文件待同步")
|
|
|
|
success_count = 0
|
|
for filepath in sorted(files_to_sync):
|
|
if not filepath.exists():
|
|
print(f"⚠️ 文件不存在:{filepath}")
|
|
continue
|
|
|
|
print(f"\n📄 处理:{filepath.name}")
|
|
report_data = parse_markdown_report(filepath)
|
|
|
|
if not report_data['year'] or not report_data['week_number']:
|
|
print(f"⚠️ 无法解析年份/周数,跳过")
|
|
continue
|
|
|
|
if args.dry_run:
|
|
print(f" 年份:{report_data['year']}, 周数:{report_data['week_number']}")
|
|
print(f" 日期:{report_data['start_date']} ~ {report_data['end_date']}")
|
|
print(f" 项目:{report_data['project']}")
|
|
print(f" 任务:{report_data['main_task']}")
|
|
print(f" 问题:{len(report_data['problems'])} 个")
|
|
success_count += 1
|
|
else:
|
|
if sync_to_mysql(report_data):
|
|
success_count += 1
|
|
|
|
print(f"\n{'✅' if success_count == len(files_to_sync) else '⚠️'} 完成:{success_count}/{len(files_to_sync)} 个文件")
|
|
return 0 if success_count == len(files_to_sync) else 1
|
|
|
|
|
|
if __name__ == '__main__':
|
|
exit(main())
|