insert_external_staff_to_sys_user.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. 将 temp_external_staff 的外聘职工数据插入到 sys_user,并为每个新用户分配角色
  5. 规则:
  6. - sys_user.user_name 使用 temp_external_staff.`手机号`
  7. - sys_user.user_no 使用 temp_external_staff.`序号`,格式为 'W' + zero_pad(序号,5)(例如序号=1 -> W00001;序号=209 -> W00209)
  8. - sys_user.nick_name 使用 temp_external_staff.`姓名`
  9. - 性别映射:'男' -> '0', '女' -> '1',其他或空 -> NULL 或默认 '0'(脚本中采用 '0')
  10. - 为每个插入的用户在 sys_user_role 中插入两个角色:4 和 1938120323613728770
  11. 注意:脚本默认不执行(dry-run),只有当去掉 --dry-run 参数并确认后才会写库。
  12. 用法示例(不写库):
  13. python insert_external_staff_to_sys_user.py --dry-run
  14. 用法示例(写库 — 请先确认并备份):
  15. python insert_external_staff_to_sys_user.py
  16. """
  17. from __future__ import annotations
  18. import os
  19. import sys
  20. import argparse
  21. import importlib.util
  22. import datetime
  23. import re
  24. from typing import Any, Dict, Optional
  25. import pandas as pd
  26. from sqlalchemy import create_engine, text
  27. def load_db_config_from_file(config_path: str) -> dict:
  28. spec = importlib.util.spec_from_file_location("db_config", config_path)
  29. module = importlib.util.module_from_spec(spec)
  30. spec.loader.exec_module(module) # type: ignore
  31. if not hasattr(module, "DB_CONFIG"):
  32. raise RuntimeError(f"No DB_CONFIG found in {config_path}")
  33. return getattr(module, "DB_CONFIG")
  34. def build_engine_from_config(db_config: dict):
  35. user = db_config.get("user")
  36. password = db_config.get("password")
  37. host = db_config.get("host", "localhost")
  38. port = db_config.get("port", 3306)
  39. database = db_config.get("database")
  40. charset = db_config.get("charset", "utf8mb4")
  41. uri = f"mysql+pymysql://{user}:{password}@{host}:{port}/{database}?charset={charset}"
  42. return create_engine(uri, pool_pre_ping=True)
  43. def normalize_phone(raw: Optional[str]) -> Optional[str]:
  44. if raw is None:
  45. return None
  46. s = str(raw)
  47. digits = "".join(re.findall(r"\d", s))
  48. return digits[:11] if digits else None
  49. def map_sex(raw: Optional[str]) -> Optional[str]:
  50. if raw is None:
  51. return None
  52. s = str(raw).strip()
  53. if s in ("男", "M", "m", "male", "Male"):
  54. return "0"
  55. if s in ("女", "F", "f", "female", "Female"):
  56. return "1"
  57. return None
  58. def zero_pad_seq_w_prefix(seq_value: Optional[str]) -> str:
  59. """将序号转换为 'W' + 5位零填充字符串;若无法解析为整数,则尝试取数字子串或返回 'W00000'"""
  60. if seq_value is None:
  61. return "W00000"
  62. s = str(seq_value).strip()
  63. # 提取整数部分
  64. m = re.search(r"\d+", s)
  65. if not m:
  66. return "W00000"
  67. num = int(m.group(0))
  68. return "W" + str(num).zfill(5)
  69. def generate_user_id(conn) -> int:
  70. r = conn.execute(text("SELECT MAX(user_id) as m FROM sys_user")).fetchone()
  71. max_id = int(r[0]) if r and r[0] is not None else 0
  72. return max_id + 1
  73. def user_exists(conn, user_name: str) -> bool:
  74. r = conn.execute(text("SELECT 1 FROM sys_user WHERE user_name = :u LIMIT 1"), {"u": user_name}).fetchone()
  75. return bool(r)
  76. def user_has_role(conn, user_id: int, role_id: int) -> bool:
  77. 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()
  78. return bool(r)
  79. def insert_user(conn, data: Dict[str, Any]):
  80. cols = ", ".join(f"`{k}`" for k in data.keys())
  81. vals = ", ".join(f":{k}" for k in data.keys())
  82. ins = text(f"INSERT INTO sys_user ({cols}) VALUES ({vals})")
  83. conn.execute(ins, data)
  84. def insert_user_role(conn, user_id: int, role_id: int):
  85. ins = text("INSERT INTO sys_user_role (`user_id`, `role_id`) VALUES (:uid, :rid)")
  86. conn.execute(ins, {"uid": user_id, "rid": role_id})
  87. def main():
  88. parser = argparse.ArgumentParser(description="将 temp_external_staff 的数据插入 sys_user")
  89. parser.add_argument("--db-config", type=str, default="../学校历史数据迁移/db_config.py", help="db_config.py 路径")
  90. parser.add_argument("--table", type=str, default="temp_external_staff", help="源临时表名")
  91. parser.add_argument("--dry-run", action="store_true", help="仅打印将要执行的操作,不写入数据库")
  92. args = parser.parse_args()
  93. base_dir = os.path.dirname(os.path.abspath(__file__))
  94. db_config_path = args.db_config if os.path.isabs(args.db_config) else os.path.join(base_dir, args.db_config)
  95. if not os.path.exists(db_config_path):
  96. # 尝试上级目录中的学校历史数据迁移配置
  97. alt = os.path.join(os.path.dirname(base_dir), "学校历史数据迁移", "db_config.py")
  98. if os.path.exists(alt):
  99. db_config_path = alt
  100. else:
  101. print(f"找不到 db_config 文件: {db_config_path}")
  102. sys.exit(1)
  103. db_config = load_db_config_from_file(db_config_path)
  104. engine = build_engine_from_config(db_config)
  105. # 读取源数据
  106. with engine.connect() as conn:
  107. df = pd.read_sql(text(f"SELECT * FROM `{args.table}`"), conn)
  108. # 识别列名
  109. possible_seq_cols = ["序号", "序号_1", "col_1"]
  110. possible_name_cols = ["姓名", "name"]
  111. possible_phone_cols = ["手机号", "手机号_常用的一个_", "phone"]
  112. possible_sex_cols = ["性别", "sex"]
  113. possible_dept_cols = ["部门", "dept", "department"]
  114. cols = list(df.columns)
  115. def pick(names):
  116. for n in names:
  117. if n in cols:
  118. return n
  119. return None
  120. seq_col = pick(possible_seq_cols) or cols[1] if len(cols) > 1 else None
  121. name_col = pick(possible_name_cols) or cols[2] if len(cols) > 2 else None
  122. phone_col = pick(possible_phone_cols) or cols[6] if len(cols) > 6 else None
  123. sex_col = pick(possible_sex_cols) or cols[3] if len(cols) > 3 else None
  124. dept_col = pick(possible_dept_cols) or cols[5] if len(cols) > 5 else None
  125. print(f"使用列映射: 序号={seq_col}, 姓名={name_col}, 性别={sex_col}, 手机号={phone_col}")
  126. inserted = 0
  127. skipped_exists = 0
  128. errors = 0
  129. role_ids = [4, 1938120323613728770]
  130. password_hash = "$2a$10$FGldU5BGGq9ONuBmYhxLweFhmbkE45keMKASNZpjETXawE1oGbtZy"
  131. now_str = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  132. # iterate and insert
  133. with engine.begin() as conn:
  134. for idx, row in df.iterrows():
  135. seq_val = row.get(seq_col) if seq_col in row else None
  136. name_val = row.get(name_col) if name_col in row else None
  137. raw_phone = row.get(phone_col) if phone_col in row else None
  138. dept_raw = row.get(dept_col) if dept_col in row else None
  139. sex_raw = row.get(sex_col) if sex_col in row else None
  140. phone = normalize_phone(raw_phone)
  141. if not phone:
  142. print(f"跳过第{idx+1}行:手机号无效或为空(原值: {raw_phone})")
  143. errors += 1
  144. continue
  145. user_name_val = phone
  146. if user_exists(conn, user_name_val):
  147. skipped_exists += 1
  148. continue
  149. user_no_val = zero_pad_seq_w_prefix(seq_val)
  150. nick = str(name_val).strip() if name_val is not None else user_name_val
  151. sex_val = map_sex(sex_raw) or "0"
  152. # resolve dept_id from 部门名称
  153. dept_id = None
  154. if dept_raw is not None and str(dept_raw).strip() != "":
  155. try:
  156. r = conn.execute(text("SELECT dept_id FROM sys_dept WHERE dept_name = :name LIMIT 1"), {"name": str(dept_raw).strip()}).fetchone()
  157. dept_id = int(r[0]) if r and r[0] is not None else None
  158. if dept_id is None:
  159. print(f"警告:第{idx+1}行,部门 '{dept_raw}' 在 sys_dept 中未找到,dept_id 将设为 NULL。")
  160. except Exception as e:
  161. print(f"查找部门时出错(行 {idx+1}): {e}")
  162. dept_id = None
  163. new_user_id = generate_user_id(conn)
  164. user_record = {
  165. "user_id": new_user_id,
  166. "tenant_id": "000000",
  167. "dept_id": dept_id,
  168. "user_name": user_name_val,
  169. "nick_name": nick,
  170. "user_type": "sys_user",
  171. "email": None,
  172. "phonenumber": phone,
  173. "sex": sex_val,
  174. "avatar": None,
  175. "password": password_hash,
  176. "status": "0",
  177. "del_flag": "0",
  178. "login_ip": "",
  179. "login_date": None,
  180. "create_dept": 103,
  181. "create_by": 1,
  182. "create_time": now_str,
  183. "update_by": 1,
  184. "update_time": now_str,
  185. "remark": None,
  186. "account_type": "1",
  187. "user_no": user_no_val,
  188. }
  189. print(f"准备插入 user_id={new_user_id}, user_name={user_name_val}, user_no={user_no_val}, nick={nick}")
  190. if args.dry_run:
  191. inserted += 1
  192. continue
  193. try:
  194. insert_user(conn, user_record)
  195. for rid in role_ids:
  196. if not user_has_role(conn, new_user_id, rid):
  197. insert_user_role(conn, new_user_id, rid)
  198. inserted += 1
  199. except Exception as e:
  200. print(f"插入失败(行 {idx+1}): {e}")
  201. errors += 1
  202. print(f"完成:插入 {inserted} 条,已存在跳过 {skipped_exists} 条,错误 {errors} 条(dry-run={args.dry_run}))")
  203. if __name__ == "__main__":
  204. main()