|
|
@@ -0,0 +1,252 @@
|
|
|
+#!/usr/bin/env python3
|
|
|
+# -*- coding: utf-8 -*-
|
|
|
+"""
|
|
|
+将 temp_external_staff 的外聘职工数据插入到 sys_user,并为每个新用户分配角色
|
|
|
+
|
|
|
+规则:
|
|
|
+ - sys_user.user_name 使用 temp_external_staff.`手机号`
|
|
|
+ - sys_user.user_no 使用 temp_external_staff.`序号`,格式为 'W' + zero_pad(序号,5)(例如序号=1 -> W00001;序号=209 -> W00209)
|
|
|
+ - sys_user.nick_name 使用 temp_external_staff.`姓名`
|
|
|
+ - 性别映射:'男' -> '0', '女' -> '1',其他或空 -> NULL 或默认 '0'(脚本中采用 '0')
|
|
|
+ - 为每个插入的用户在 sys_user_role 中插入两个角色:4 和 1938120323613728770
|
|
|
+
|
|
|
+注意:脚本默认不执行(dry-run),只有当去掉 --dry-run 参数并确认后才会写库。
|
|
|
+
|
|
|
+用法示例(不写库):
|
|
|
+ python insert_external_staff_to_sys_user.py --dry-run
|
|
|
+
|
|
|
+用法示例(写库 — 请先确认并备份):
|
|
|
+ python insert_external_staff_to_sys_user.py
|
|
|
+"""
|
|
|
+
|
|
|
+from __future__ import annotations
|
|
|
+import os
|
|
|
+import sys
|
|
|
+import argparse
|
|
|
+import importlib.util
|
|
|
+import datetime
|
|
|
+import re
|
|
|
+from typing import Any, Dict, Optional
|
|
|
+
|
|
|
+import pandas as pd
|
|
|
+from sqlalchemy import create_engine, text
|
|
|
+
|
|
|
+
|
|
|
+def load_db_config_from_file(config_path: str) -> dict:
|
|
|
+ spec = importlib.util.spec_from_file_location("db_config", config_path)
|
|
|
+ module = importlib.util.module_from_spec(spec)
|
|
|
+ spec.loader.exec_module(module) # type: ignore
|
|
|
+ if not hasattr(module, "DB_CONFIG"):
|
|
|
+ raise RuntimeError(f"No DB_CONFIG found in {config_path}")
|
|
|
+ return getattr(module, "DB_CONFIG")
|
|
|
+
|
|
|
+
|
|
|
+def build_engine_from_config(db_config: dict):
|
|
|
+ user = db_config.get("user")
|
|
|
+ password = db_config.get("password")
|
|
|
+ host = db_config.get("host", "localhost")
|
|
|
+ port = db_config.get("port", 3306)
|
|
|
+ database = db_config.get("database")
|
|
|
+ charset = db_config.get("charset", "utf8mb4")
|
|
|
+ uri = f"mysql+pymysql://{user}:{password}@{host}:{port}/{database}?charset={charset}"
|
|
|
+ return create_engine(uri, pool_pre_ping=True)
|
|
|
+
|
|
|
+
|
|
|
+def normalize_phone(raw: Optional[str]) -> Optional[str]:
|
|
|
+ if raw is None:
|
|
|
+ return None
|
|
|
+ s = str(raw)
|
|
|
+ digits = "".join(re.findall(r"\d", s))
|
|
|
+ return digits[:11] if digits else None
|
|
|
+
|
|
|
+
|
|
|
+def map_sex(raw: Optional[str]) -> Optional[str]:
|
|
|
+ if raw is None:
|
|
|
+ return None
|
|
|
+ s = str(raw).strip()
|
|
|
+ if s in ("男", "M", "m", "male", "Male"):
|
|
|
+ return "0"
|
|
|
+ if s in ("女", "F", "f", "female", "Female"):
|
|
|
+ return "1"
|
|
|
+ return None
|
|
|
+
|
|
|
+
|
|
|
+def zero_pad_seq_w_prefix(seq_value: Optional[str]) -> str:
|
|
|
+ """将序号转换为 'W' + 5位零填充字符串;若无法解析为整数,则尝试取数字子串或返回 'W00000'"""
|
|
|
+ if seq_value is None:
|
|
|
+ return "W00000"
|
|
|
+ s = str(seq_value).strip()
|
|
|
+ # 提取整数部分
|
|
|
+ m = re.search(r"\d+", s)
|
|
|
+ if not m:
|
|
|
+ return "W00000"
|
|
|
+ num = int(m.group(0))
|
|
|
+ return "W" + str(num).zfill(5)
|
|
|
+
|
|
|
+
|
|
|
+def generate_user_id(conn) -> int:
|
|
|
+ r = conn.execute(text("SELECT MAX(user_id) as m FROM sys_user")).fetchone()
|
|
|
+ max_id = int(r[0]) if r and r[0] is not None else 0
|
|
|
+ return max_id + 1
|
|
|
+
|
|
|
+
|
|
|
+def user_exists(conn, user_name: str) -> bool:
|
|
|
+ r = conn.execute(text("SELECT 1 FROM sys_user WHERE user_name = :u LIMIT 1"), {"u": user_name}).fetchone()
|
|
|
+ return bool(r)
|
|
|
+
|
|
|
+
|
|
|
+def user_has_role(conn, user_id: int, role_id: int) -> bool:
|
|
|
+ r = conn.execute(text("SELECT 1 FROM sys_user_role WHERE user_id = :uid AND role_id = :rid LIMIT 1"), {"uid": user_id, "rid": role_id}).fetchone()
|
|
|
+ return bool(r)
|
|
|
+
|
|
|
+
|
|
|
+def insert_user(conn, data: Dict[str, Any]):
|
|
|
+ cols = ", ".join(f"`{k}`" for k in data.keys())
|
|
|
+ vals = ", ".join(f":{k}" for k in data.keys())
|
|
|
+ ins = text(f"INSERT INTO sys_user ({cols}) VALUES ({vals})")
|
|
|
+ conn.execute(ins, data)
|
|
|
+
|
|
|
+
|
|
|
+def insert_user_role(conn, user_id: int, role_id: int):
|
|
|
+ ins = text("INSERT INTO sys_user_role (`user_id`, `role_id`) VALUES (:uid, :rid)")
|
|
|
+ conn.execute(ins, {"uid": user_id, "rid": role_id})
|
|
|
+
|
|
|
+
|
|
|
+def main():
|
|
|
+ parser = argparse.ArgumentParser(description="将 temp_external_staff 的数据插入 sys_user")
|
|
|
+ parser.add_argument("--db-config", type=str, default="../学校历史数据迁移/db_config.py", help="db_config.py 路径")
|
|
|
+ parser.add_argument("--table", type=str, default="temp_external_staff", help="源临时表名")
|
|
|
+ parser.add_argument("--dry-run", action="store_true", help="仅打印将要执行的操作,不写入数据库")
|
|
|
+ args = parser.parse_args()
|
|
|
+
|
|
|
+ base_dir = os.path.dirname(os.path.abspath(__file__))
|
|
|
+ db_config_path = args.db_config if os.path.isabs(args.db_config) else os.path.join(base_dir, args.db_config)
|
|
|
+ if not os.path.exists(db_config_path):
|
|
|
+ # 尝试上级目录中的学校历史数据迁移配置
|
|
|
+ alt = os.path.join(os.path.dirname(base_dir), "学校历史数据迁移", "db_config.py")
|
|
|
+ if os.path.exists(alt):
|
|
|
+ db_config_path = alt
|
|
|
+ else:
|
|
|
+ print(f"找不到 db_config 文件: {db_config_path}")
|
|
|
+ sys.exit(1)
|
|
|
+
|
|
|
+ db_config = load_db_config_from_file(db_config_path)
|
|
|
+ engine = build_engine_from_config(db_config)
|
|
|
+
|
|
|
+ # 读取源数据
|
|
|
+ with engine.connect() as conn:
|
|
|
+ df = pd.read_sql(text(f"SELECT * FROM `{args.table}`"), conn)
|
|
|
+
|
|
|
+ # 识别列名
|
|
|
+ possible_seq_cols = ["序号", "序号_1", "col_1"]
|
|
|
+ possible_name_cols = ["姓名", "name"]
|
|
|
+ possible_phone_cols = ["手机号", "手机号_常用的一个_", "phone"]
|
|
|
+ possible_sex_cols = ["性别", "sex"]
|
|
|
+ possible_dept_cols = ["部门", "dept", "department"]
|
|
|
+
|
|
|
+ cols = list(df.columns)
|
|
|
+ def pick(names):
|
|
|
+ for n in names:
|
|
|
+ if n in cols:
|
|
|
+ return n
|
|
|
+ return None
|
|
|
+
|
|
|
+ seq_col = pick(possible_seq_cols) or cols[1] if len(cols) > 1 else None
|
|
|
+ name_col = pick(possible_name_cols) or cols[2] if len(cols) > 2 else None
|
|
|
+ phone_col = pick(possible_phone_cols) or cols[6] if len(cols) > 6 else None
|
|
|
+ sex_col = pick(possible_sex_cols) or cols[3] if len(cols) > 3 else None
|
|
|
+ dept_col = pick(possible_dept_cols) or cols[5] if len(cols) > 5 else None
|
|
|
+
|
|
|
+ print(f"使用列映射: 序号={seq_col}, 姓名={name_col}, 性别={sex_col}, 手机号={phone_col}")
|
|
|
+
|
|
|
+ inserted = 0
|
|
|
+ skipped_exists = 0
|
|
|
+ errors = 0
|
|
|
+ role_ids = [4, 1938120323613728770]
|
|
|
+ password_hash = "$2a$10$FGldU5BGGq9ONuBmYhxLweFhmbkE45keMKASNZpjETXawE1oGbtZy"
|
|
|
+ now_str = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
|
+
|
|
|
+ # iterate and insert
|
|
|
+ with engine.begin() as conn:
|
|
|
+ for idx, row in df.iterrows():
|
|
|
+ seq_val = row.get(seq_col) if seq_col in row else None
|
|
|
+ name_val = row.get(name_col) if name_col in row else None
|
|
|
+ raw_phone = row.get(phone_col) if phone_col in row else None
|
|
|
+ dept_raw = row.get(dept_col) if dept_col in row else None
|
|
|
+ sex_raw = row.get(sex_col) if sex_col in row else None
|
|
|
+
|
|
|
+ phone = normalize_phone(raw_phone)
|
|
|
+ if not phone:
|
|
|
+ print(f"跳过第{idx+1}行:手机号无效或为空(原值: {raw_phone})")
|
|
|
+ errors += 1
|
|
|
+ continue
|
|
|
+
|
|
|
+ user_name_val = phone
|
|
|
+ if user_exists(conn, user_name_val):
|
|
|
+ skipped_exists += 1
|
|
|
+ continue
|
|
|
+
|
|
|
+ user_no_val = zero_pad_seq_w_prefix(seq_val)
|
|
|
+ nick = str(name_val).strip() if name_val is not None else user_name_val
|
|
|
+ sex_val = map_sex(sex_raw) or "0"
|
|
|
+
|
|
|
+ # resolve dept_id from 部门名称
|
|
|
+ dept_id = None
|
|
|
+ if dept_raw is not None and str(dept_raw).strip() != "":
|
|
|
+ try:
|
|
|
+ r = conn.execute(text("SELECT dept_id FROM sys_dept WHERE dept_name = :name LIMIT 1"), {"name": str(dept_raw).strip()}).fetchone()
|
|
|
+ dept_id = int(r[0]) if r and r[0] is not None else None
|
|
|
+ if dept_id is None:
|
|
|
+ print(f"警告:第{idx+1}行,部门 '{dept_raw}' 在 sys_dept 中未找到,dept_id 将设为 NULL。")
|
|
|
+ except Exception as e:
|
|
|
+ print(f"查找部门时出错(行 {idx+1}): {e}")
|
|
|
+ dept_id = None
|
|
|
+
|
|
|
+ new_user_id = generate_user_id(conn)
|
|
|
+ user_record = {
|
|
|
+ "user_id": new_user_id,
|
|
|
+ "tenant_id": "000000",
|
|
|
+ "dept_id": dept_id,
|
|
|
+ "user_name": user_name_val,
|
|
|
+ "nick_name": nick,
|
|
|
+ "user_type": "sys_user",
|
|
|
+ "email": None,
|
|
|
+ "phonenumber": phone,
|
|
|
+ "sex": sex_val,
|
|
|
+ "avatar": None,
|
|
|
+ "password": password_hash,
|
|
|
+ "status": "0",
|
|
|
+ "del_flag": "0",
|
|
|
+ "login_ip": "",
|
|
|
+ "login_date": None,
|
|
|
+ "create_dept": 103,
|
|
|
+ "create_by": 1,
|
|
|
+ "create_time": now_str,
|
|
|
+ "update_by": 1,
|
|
|
+ "update_time": now_str,
|
|
|
+ "remark": None,
|
|
|
+ "account_type": "1",
|
|
|
+ "user_no": user_no_val,
|
|
|
+ }
|
|
|
+
|
|
|
+ print(f"准备插入 user_id={new_user_id}, user_name={user_name_val}, user_no={user_no_val}, nick={nick}")
|
|
|
+ if args.dry_run:
|
|
|
+ inserted += 1
|
|
|
+ continue
|
|
|
+
|
|
|
+ try:
|
|
|
+ insert_user(conn, user_record)
|
|
|
+ for rid in role_ids:
|
|
|
+ if not user_has_role(conn, new_user_id, rid):
|
|
|
+ insert_user_role(conn, new_user_id, rid)
|
|
|
+ inserted += 1
|
|
|
+ except Exception as e:
|
|
|
+ print(f"插入失败(行 {idx+1}): {e}")
|
|
|
+ errors += 1
|
|
|
+
|
|
|
+ print(f"完成:插入 {inserted} 条,已存在跳过 {skipped_exists} 条,错误 {errors} 条(dry-run={args.dry_run}))")
|
|
|
+
|
|
|
+
|
|
|
+if __name__ == "__main__":
|
|
|
+ main()
|
|
|
+
|