提交
This commit is contained in:
1 parent
48109ff1e7
commit
4e34c7eb36
60 files changed
+9583
-139
No files matched your search
+228
-96
@@ -14,6 +14,8 @@ from typing import Dict, Any, Optional, List, Tuple
|
||||
|
||||
import bcrypt
|
||||
import pymysql
|
||||
import pymysql.cursors
|
||||
from pymysql.cursors import DictCursor
|
||||
import requests
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import unpad
|
||||
@@ -52,6 +54,10 @@ class ChaoxingStudentAPI:
|
||||
self.api_path = "xsjbxxpi_66247"
|
||||
# 生成AES解密密钥:appId去除'-'后取前16位
|
||||
self.aes_key = app_id.replace("-", "")[:16].encode('utf-8')
|
||||
# 数据库连接对象
|
||||
self._db_connection = None
|
||||
self._last_ping_time = 0
|
||||
self._ping_interval = 60 # 每60秒ping一次数据库
|
||||
|
||||
def generate_enc(self, page_num: int, page_size: int) -> str:
|
||||
"""
|
||||
@@ -254,6 +260,100 @@ class ChaoxingStudentAPI:
|
||||
print(f"\n总共获取 {len(all_data)} 条学生信息")
|
||||
return all_data
|
||||
|
||||
def _get_db_connection(self, force_new: bool = False):
|
||||
"""
|
||||
获取数据库连接,自动处理重连
|
||||
|
||||
Args:
|
||||
force_new: 是否强制创建新连接
|
||||
|
||||
Returns:
|
||||
数据库连接对象
|
||||
"""
|
||||
if force_new and self._db_connection:
|
||||
try:
|
||||
self._db_connection.close()
|
||||
except:
|
||||
pass
|
||||
self._db_connection = None
|
||||
|
||||
if self._db_connection is None:
|
||||
print("创建新的数据库连接...")
|
||||
self._db_connection = pymysql.connect(
|
||||
**DB_CONFIG,
|
||||
cursorclass=DictCursor,
|
||||
autocommit=False,
|
||||
connect_timeout=30,
|
||||
read_timeout=60,
|
||||
write_timeout=60
|
||||
)
|
||||
self._last_ping_time = time.time()
|
||||
print("✓ 数据库连接已建立")
|
||||
else:
|
||||
# 检查连接是否需要ping
|
||||
current_time = time.time()
|
||||
if current_time - self._last_ping_time > self._ping_interval:
|
||||
try:
|
||||
self._db_connection.ping(reconnect=True)
|
||||
self._last_ping_time = current_time
|
||||
except Exception as e:
|
||||
print(f"连接ping失败,重新建立连接: {e}")
|
||||
self._db_connection = None
|
||||
return self._get_db_connection(force_new=True)
|
||||
|
||||
return self._db_connection
|
||||
|
||||
def _close_db_connection(self):
|
||||
"""
|
||||
关闭数据库连接
|
||||
"""
|
||||
if self._db_connection:
|
||||
try:
|
||||
self._db_connection.close()
|
||||
print("✓ 数据库连接已关闭")
|
||||
except:
|
||||
pass
|
||||
self._db_connection = None
|
||||
|
||||
def _execute_with_retry(self, func, max_retries: int = 3):
|
||||
"""
|
||||
执行数据库操作并在失败时重试
|
||||
|
||||
Args:
|
||||
func: 要执行的函数,接收connection参数
|
||||
max_retries: 最大重试次数
|
||||
|
||||
Returns:
|
||||
函数执行结果
|
||||
"""
|
||||
last_error = None
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
connection = self._get_db_connection()
|
||||
result = func(connection)
|
||||
return result
|
||||
except (pymysql.err.OperationalError, pymysql.err.InterfaceError) as e:
|
||||
last_error = e
|
||||
error_code = e.args[0] if e.args else 0
|
||||
# 2013: Lost connection to MySQL server during query
|
||||
# 2006: MySQL server has gone away
|
||||
# 1053: Server shutdown in progress
|
||||
if error_code in (2013, 2006, 1053):
|
||||
print(f"数据库连接错误 (尝试 {attempt+1}/{max_retries}): {e}")
|
||||
self._db_connection = None
|
||||
if attempt < max_retries - 1:
|
||||
wait_time = (attempt + 1) * 2 # 递增等待时间
|
||||
print(f"等待 {wait_time} 秒后重试...")
|
||||
time.sleep(wait_time)
|
||||
continue
|
||||
else:
|
||||
raise
|
||||
except Exception as e:
|
||||
print(f"数据库操作失败: {e}")
|
||||
raise
|
||||
|
||||
raise last_error if last_error else Exception("数据库操作失败")
|
||||
|
||||
def clear_table(self) -> bool:
|
||||
"""
|
||||
清空学生数据表
|
||||
@@ -380,70 +480,70 @@ class ChaoxingStudentAPI:
|
||||
total_pages = None
|
||||
|
||||
try:
|
||||
connection = pymysql.connect(**DB_CONFIG)
|
||||
try:
|
||||
current_time = int(time.time() * 1000) # 毫秒时间戳
|
||||
table_name = TABLE_MAPPING["student"]
|
||||
current_time = int(time.time() * 1000) # 毫秒时间戳
|
||||
table_name = TABLE_MAPPING["student"]
|
||||
|
||||
# 构建插入SQL
|
||||
insert_sql = f"""
|
||||
INSERT INTO `{table_name}` (
|
||||
`inserttime`, `updatetime`,
|
||||
`xsjbxx_xm`, `xsjbxx_xh`, `xsjbxx_lxdh`, `xsjbxx_sfzjlx`,
|
||||
`xsjbxx_sfzjh`, `dataMigrateUniqueId`, `xsjbxx_bjxx`, `xsjbxx_bjbh`,
|
||||
`xsjbxx_szxq`, `xsjbxx_xqdm`, `xsjbxx_sznj`, `xsjbxx_rxnj`,
|
||||
`xsjbxx_yxxx`, `xsjbxx_yxdm`, `xsjbxx_zyxx`, `xsjbxx_zydm`,
|
||||
`xsjbxx_bzrgh`, `xsjbxx_xsdqzt`, `xsjbxx_xssfzx`, `xsjbxx_xjydlx`,
|
||||
`xsjbxx_pycc`, `xsjbxx_xz`, `xsjbxx_xb`, `xsjbxx_csrq`,
|
||||
`xsjbxx_gj`, `xsjbxx_jtzz`, `xsjbxx_jtyzbm`, `xsjbxx_jtlxr`,
|
||||
`xsjbxx_jtdh`, `xsjbxx_fmxm1`, `xsjbxx_fmxm2`, `xsjbxx_cygx1`,
|
||||
`xsjbxx_cygx2`, `xsjbxx_cysfwjhr1`, `xsjbxx_cysfwjhr2`,
|
||||
`xsjbxx_fmsjh1`, `xsjbxx_fmsjh2`, `xsjbxx_fmzjlx1`, `xsjbxx_fmzjlx2`,
|
||||
`xsjbxx_fmzjh1`, `xsjbxx_fmzjh2`, `xsjbxx_xmpy`, `xsjbxx_xjzt`
|
||||
) VALUES (
|
||||
%s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
|
||||
%s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
|
||||
%s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
|
||||
%s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
|
||||
%s, %s, %s, %s, %s
|
||||
)
|
||||
"""
|
||||
|
||||
while True:
|
||||
print(f"\n正在获取第 {page_num} 页数据...")
|
||||
result = self.get_student_list(page_num, max_page_size)
|
||||
|
||||
# 构建插入SQL
|
||||
insert_sql = f"""
|
||||
INSERT INTO `{table_name}` (
|
||||
`inserttime`, `updatetime`,
|
||||
`xsjbxx_xm`, `xsjbxx_xh`, `xsjbxx_lxdh`, `xsjbxx_sfzjlx`,
|
||||
`xsjbxx_sfzjh`, `dataMigrateUniqueId`, `xsjbxx_bjxx`, `xsjbxx_bjbh`,
|
||||
`xsjbxx_szxq`, `xsjbxx_xqdm`, `xsjbxx_sznj`, `xsjbxx_rxnj`,
|
||||
`xsjbxx_yxxx`, `xsjbxx_yxdm`, `xsjbxx_zyxx`, `xsjbxx_zydm`,
|
||||
`xsjbxx_bzrgh`, `xsjbxx_xsdqzt`, `xsjbxx_xssfzx`, `xsjbxx_xjydlx`,
|
||||
`xsjbxx_pycc`, `xsjbxx_xz`, `xsjbxx_xb`, `xsjbxx_csrq`,
|
||||
`xsjbxx_gj`, `xsjbxx_jtzz`, `xsjbxx_jtyzbm`, `xsjbxx_jtlxr`,
|
||||
`xsjbxx_jtdh`, `xsjbxx_fmxm1`, `xsjbxx_fmxm2`, `xsjbxx_cygx1`,
|
||||
`xsjbxx_cygx2`, `xsjbxx_cysfwjhr1`, `xsjbxx_cysfwjhr2`,
|
||||
`xsjbxx_fmsjh1`, `xsjbxx_fmsjh2`, `xsjbxx_fmzjlx1`, `xsjbxx_fmzjlx2`,
|
||||
`xsjbxx_fmzjh1`, `xsjbxx_fmzjh2`, `xsjbxx_xmpy`, `xsjbxx_xjzt`
|
||||
) VALUES (
|
||||
%s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
|
||||
%s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
|
||||
%s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
|
||||
%s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
|
||||
%s, %s, %s, %s, %s
|
||||
)
|
||||
"""
|
||||
# 超星API成功时code=1,失败时code!=1
|
||||
if result.get("code") != 1:
|
||||
print(f"获取第 {page_num} 页失败: {result.get('message', '未知错误')}")
|
||||
break
|
||||
|
||||
with connection.cursor() as cursor:
|
||||
while True:
|
||||
print(f"\n正在获取第 {page_num} 页数据...")
|
||||
result = self.get_student_list(page_num, max_page_size)
|
||||
|
||||
# 超星API成功时code=1,失败时code!=1
|
||||
if result.get("code") != 1:
|
||||
print(f"获取第 {page_num} 页失败: {result.get('message', '未知错误')}")
|
||||
break
|
||||
|
||||
# 获取分页信息(从extendData中)
|
||||
extend_data = result.get("extendData", {})
|
||||
if total_pages is None and extend_data:
|
||||
total_pages = int(extend_data.get("pages", 0))
|
||||
total_count = int(extend_data.get("count", 0))
|
||||
print(f"总页数: {total_pages}, 总条数: {total_count}")
|
||||
|
||||
data = result.get("data")
|
||||
if not data:
|
||||
print("没有更多数据")
|
||||
break
|
||||
|
||||
# 处理返回的数据
|
||||
items = []
|
||||
if isinstance(data, list):
|
||||
items = data
|
||||
elif isinstance(data, dict):
|
||||
items = data.get("list", data.get("records", []))
|
||||
|
||||
if not items:
|
||||
break
|
||||
|
||||
print(f"第 {page_num} 页获取 {len(items)} 条数据")
|
||||
|
||||
# 逐条插入数据
|
||||
# 获取分页信息(从extendData中)
|
||||
extend_data = result.get("extendData", {})
|
||||
if total_pages is None and extend_data:
|
||||
total_pages = int(extend_data.get("pages", 0))
|
||||
total_count = int(extend_data.get("count", 0))
|
||||
print(f"总页数: {total_pages}, 总条数: {total_count}")
|
||||
|
||||
data = result.get("data")
|
||||
if not data:
|
||||
print("没有更多数据")
|
||||
break
|
||||
|
||||
# 处理返回的数据
|
||||
items = []
|
||||
if isinstance(data, list):
|
||||
items = data
|
||||
elif isinstance(data, dict):
|
||||
items = data.get("list", data.get("records", []))
|
||||
|
||||
if not items:
|
||||
break
|
||||
|
||||
print(f"第 {page_num} 页获取 {len(items)} 条数据")
|
||||
|
||||
# 使用重试机制插入数据
|
||||
def insert_batch(connection):
|
||||
nonlocal total_saved
|
||||
with connection.cursor() as cursor:
|
||||
for item in items:
|
||||
try:
|
||||
cursor.execute(insert_sql, (
|
||||
@@ -469,28 +569,33 @@ class ChaoxingStudentAPI:
|
||||
except Exception as e:
|
||||
print(f"插入单条数据失败: {e}, 数据: {item}")
|
||||
|
||||
# 如果知道总页数,检查是否已获取完所有页
|
||||
if total_pages and page_num >= total_pages:
|
||||
break
|
||||
# 如果返回的数据少于page_size,说明已经是最后一页
|
||||
if len(items) < max_page_size:
|
||||
break
|
||||
|
||||
page_num += 1
|
||||
# 提交当前批次剩余数据
|
||||
if total_saved % batch_size != 0:
|
||||
connection.commit()
|
||||
|
||||
# 提交剩余数据
|
||||
if total_saved % batch_size != 0:
|
||||
connection.commit()
|
||||
remaining = total_saved % batch_size
|
||||
print(f"✓ 分批提交剩余 {remaining} 条数据到表 {table_name},累计 {total_saved} 条")
|
||||
|
||||
print(f"\n✓ 总共成功保存 {total_saved} 条数据到表 {table_name}")
|
||||
return total_saved
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
# 执行插入操作(带重试)
|
||||
self._execute_with_retry(insert_batch)
|
||||
|
||||
# 如果知道总页数,检查是否已获取完所有页
|
||||
if total_pages and page_num >= total_pages:
|
||||
break
|
||||
# 如果返回的数据少于page_size,说明已经是最后一页
|
||||
if len(items) < max_page_size:
|
||||
break
|
||||
|
||||
page_num += 1
|
||||
|
||||
print(f"\n✓ 总共成功保存 {total_saved} 条数据到表 {table_name}")
|
||||
return total_saved
|
||||
except Exception as e:
|
||||
print(f"✗ 获取并保存数据失败: {e}")
|
||||
return 0
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return total_saved # 返回已保存的数量
|
||||
finally:
|
||||
self._close_db_connection()
|
||||
|
||||
# --------- 新增的方法:同步到xg_student_msg ---------
|
||||
|
||||
@@ -502,28 +607,33 @@ class ChaoxingStudentAPI:
|
||||
成功同步的记录数
|
||||
"""
|
||||
try:
|
||||
connection = pymysql.connect(**DB_CONFIG, cursorclass=pymysql.cursors.DictCursor)
|
||||
try:
|
||||
def do_sync(connection):
|
||||
source_rows = self._fetch_source_students(connection)
|
||||
if not source_rows:
|
||||
print("cx_xssj表暂无可同步数据")
|
||||
return 0
|
||||
return 0, 0, 0, 0
|
||||
|
||||
print(f"待同步数据量: {len(source_rows)} 条")
|
||||
|
||||
success, user_inserted, user_skipped, role_inserted = self._insert_into_target(connection, source_rows)
|
||||
|
||||
connection.commit()
|
||||
print(f"✓ 已同步 {success}/{len(source_rows)} 条数据至xg_student_msg")
|
||||
if success:
|
||||
print(f"✓ 新增 {user_inserted} 个sys_user账号,跳过 {user_skipped} 个已存在账号")
|
||||
print(f"✓ 新增 {role_inserted} 条学生角色绑定 (role_id={self.STUDENT_ROLE_ID})")
|
||||
return success
|
||||
finally:
|
||||
connection.close()
|
||||
return success, user_inserted, user_skipped, role_inserted
|
||||
|
||||
success, user_inserted, user_skipped, role_inserted = self._execute_with_retry(do_sync)
|
||||
|
||||
print(f"✓ 已同步 {success} 条数据至xg_student_msg")
|
||||
if success:
|
||||
print(f"✓ 新增 {user_inserted} 个sys_user账号,跳过 {user_skipped} 个已存在账号")
|
||||
print(f"✓ 新增 {role_inserted} 条学生角色绑定 (role_id={self.STUDENT_ROLE_ID})")
|
||||
return success
|
||||
except Exception as exc:
|
||||
print(f"✗ 同步到xg_student_msg失败: {exc}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return 0
|
||||
finally:
|
||||
self._close_db_connection()
|
||||
|
||||
def _fetch_source_students(self, connection) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
@@ -600,7 +710,7 @@ class ChaoxingStudentAPI:
|
||||
|
||||
unique_id = self._generate_student_id(timestamp_str, sequence)
|
||||
gender = self._map_gender(row.get("xsjbxx_xb"))
|
||||
status = self._map_student_status(row.get("xsjbxx_xjzt"))
|
||||
status = self._map_student_status(row.get("xsjbxx_xsdqzt"))
|
||||
|
||||
class_id, class_name = self._find_class_dept(
|
||||
connection,
|
||||
@@ -669,6 +779,11 @@ class ChaoxingStudentAPI:
|
||||
role_inserted_total += self._ensure_user_role(connection, student_profile["user_id"], self.STUDENT_ROLE_ID)
|
||||
|
||||
if batch_inserted >= 100:
|
||||
# 批量提交前ping一下连接
|
||||
try:
|
||||
connection.ping(reconnect=True)
|
||||
except Exception as e:
|
||||
print(f"警告: 连接ping失败: {e}")
|
||||
connection.commit()
|
||||
print("✓ xg_student_msg分批提交100条记录")
|
||||
batch_inserted = 0
|
||||
@@ -709,13 +824,28 @@ class ChaoxingStudentAPI:
|
||||
|
||||
def _map_student_status(self, status_text: Optional[str]) -> Optional[int]:
|
||||
"""
|
||||
将学籍状态文本映射为目标表字段
|
||||
将学生当前状态(xsjbxx_xsdqzt)文本映射为目标表字段
|
||||
"""
|
||||
if status_text == "在籍":
|
||||
if not status_text:
|
||||
return 1
|
||||
if status_text == "不在籍":
|
||||
return 6
|
||||
return None
|
||||
|
||||
mapping = {
|
||||
"毕业": 5,
|
||||
"退学": 4,
|
||||
"结业": 7,
|
||||
"在读": 1,
|
||||
"停学": 8,
|
||||
"分流": 9,
|
||||
"休学": 2,
|
||||
"复学": 1,
|
||||
}
|
||||
|
||||
normalized = str(status_text).strip()
|
||||
mapped_value = mapping.get(normalized)
|
||||
if mapped_value is None:
|
||||
print(f"警告: 未配置的学生当前状态 -> raw='{status_text}',默认使用1(在读)")
|
||||
return 1
|
||||
return mapped_value
|
||||
|
||||
def _find_class_dept(
|
||||
self,
|
||||
@@ -889,6 +1019,8 @@ class ChaoxingStudentAPI:
|
||||
return 1, 0
|
||||
except Exception as exc:
|
||||
print(f"插入sys_user失败: {exc}, user_id={user_id}, student_no={student['student_no']}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return 0, 1
|
||||
|
||||
def _ensure_user_role(self, connection, user_id: int, role_id: int) -> int:
|
||||
|
||||
Reference in new issue
Block a user