提交
This commit is contained in:
1 parent
3c629d2d58
commit
34c33ebc54
12 files changed
+1206
No files matched your search
@@ -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()
|
||||
|
||||
Reference in new issue
Block a user