请求专业信息API.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. """
  4. 超星系统 - 专业信息完整同步脚本
  5. 功能:
  6. 1. 从超星API获取专业信息(API路径: ysjpi_71522/list)
  7. 2. 保存到cx_zysj表
  8. 3. 从cx_zysj表同步到sys_dept表(学工系统部门表)
  9. 同步规则:
  10. - 筛选条件:zysj_kbzt='是'
  11. - 判断是否已存在:dept_category='zy-' + zysj_zybh AND dept_name=zysj_zymc
  12. - 父部门查找:通过 dept_category='xy-' + zysj_xbdm AND dept_name=zysj_ssyx 找到父dept_id
  13. - 插入到父部门下
  14. """
  15. import hashlib
  16. import base64
  17. import json
  18. import requests
  19. import pymysql
  20. import time
  21. from datetime import datetime
  22. from typing import Dict, Any, Optional, List
  23. from Crypto.Cipher import AES
  24. from Crypto.Util.Padding import unpad
  25. from db_config import DB_CONFIG, TABLE_MAPPING
  26. class ChaoxingMajorAPI:
  27. """超星专业信息API请求类"""
  28. def __init__(self, app_id: str, app_key: str, fid: str = "19628"):
  29. """
  30. 初始化API客户端
  31. Args:
  32. app_id: API的appId
  33. app_key: API的appKey
  34. fid: 单位ID,默认19628
  35. """
  36. self.app_id = app_id
  37. self.app_key = app_key
  38. self.fid = fid
  39. self.base_url = "https://appswh.chaoxing.com/biapi/api"
  40. self.api_path = "ysjpi_71522"
  41. # 生成AES解密密钥:appId去除'-'后取前16位
  42. self.aes_key = app_id.replace("-", "")[:16].encode('utf-8')
  43. def generate_enc(self, page_num: int, page_size: int) -> str:
  44. """
  45. 生成enc加密验证串
  46. enc加密方式:32位大写的MD5字符串
  47. 格式:[apiPath=ysjpi_71522#$pageNum=页码#$pageSize=单页条数#$fid=19628#$appKey=授权appKey]
  48. Args:
  49. page_num: 页码
  50. page_size: 单页条数
  51. Returns:
  52. 32位大写MD5字符串
  53. """
  54. # 构建加密字符串
  55. enc_string = f"[apiPath={self.api_path}#$pageNum={page_num}#$pageSize={page_size}#$fid={self.fid}#$appKey={self.app_key}]"
  56. # MD5加密并转为大写
  57. md5_hash = hashlib.md5(enc_string.encode('utf-8')).hexdigest().upper()
  58. return md5_hash
  59. def decrypt_data(self, ciphertext: str) -> Any:
  60. """
  61. AES解密data字段
  62. 解密算法:AES/ECB/PKCS5Padding
  63. 密钥:appId去除'-'后前16位字符
  64. Args:
  65. ciphertext: Base64编码的密文
  66. Returns:
  67. 解密后的数据(可能是JSON字符串,需要进一步解析)
  68. """
  69. try:
  70. # Base64解码
  71. encrypted_bytes = base64.b64decode(ciphertext)
  72. # AES解密
  73. cipher = AES.new(self.aes_key, AES.MODE_ECB)
  74. decrypted_bytes = cipher.decrypt(encrypted_bytes)
  75. # PKCS5Padding去填充
  76. decrypted_bytes = unpad(decrypted_bytes, AES.block_size)
  77. # 转为字符串
  78. decrypted_text = decrypted_bytes.decode('utf-8')
  79. # 尝试解析为JSON
  80. try:
  81. return json.loads(decrypted_text)
  82. except json.JSONDecodeError:
  83. # 如果不是JSON,直接返回字符串
  84. return decrypted_text
  85. except Exception as e:
  86. print(f"解密失败: {e}")
  87. return None
  88. def get_major_list(self, page_num: int = 1, page_size: int = 100) -> Dict[str, Any]:
  89. """
  90. 获取专业信息列表
  91. Args:
  92. page_num: 页码,默认1
  93. page_size: 页条数,默认100,最大1000
  94. Returns:
  95. API响应结果
  96. """
  97. # 验证page_size
  98. if page_size > 1000:
  99. page_size = 1000
  100. print("警告: pageSize超过最大限制1000,已自动调整为1000")
  101. # 生成enc加密串
  102. enc = self.generate_enc(page_num, page_size)
  103. # 构建请求URL
  104. url = f"{self.base_url}/{self.api_path}/list"
  105. # 构建请求参数
  106. params = {
  107. "pageNum": page_num,
  108. "pageSize": page_size,
  109. "fid": self.fid,
  110. "enc": enc
  111. }
  112. # 构建请求头
  113. headers = {
  114. "appId": self.app_id,
  115. "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
  116. }
  117. try:
  118. # 发送GET请求
  119. print(f"正在请求: {url}")
  120. print(f"参数: {params}")
  121. print(f"请求头appId: {self.app_id}")
  122. response = requests.get(url, params=params, headers=headers, timeout=30)
  123. # 检查HTTP状态码
  124. response.raise_for_status()
  125. # 解析JSON响应
  126. result = response.json()
  127. # 解密data字段
  128. if result.get("data") and isinstance(result.get("data"), str):
  129. decrypted_data = self.decrypt_data(result["data"])
  130. if decrypted_data is not None:
  131. result["data"] = decrypted_data
  132. else:
  133. print("警告: data字段解密失败,保持原始值")
  134. return result
  135. except requests.exceptions.RequestException as e:
  136. print(f"请求失败: {e}")
  137. return {
  138. "code": -1,
  139. "message": f"请求异常: {str(e)}",
  140. "data": None
  141. }
  142. except ValueError as e:
  143. print(f"JSON解析失败: {e}")
  144. return {
  145. "code": -1,
  146. "message": f"响应解析失败: {str(e)}",
  147. "data": None
  148. }
  149. def get_all_majors(self, max_page_size: int = 500) -> list:
  150. """
  151. 获取所有专业信息(自动分页)
  152. Args:
  153. max_page_size: 每页最大条数,默认500
  154. Returns:
  155. 所有专业信息列表
  156. """
  157. all_data = []
  158. page_num = 1
  159. total_pages = None # 总页数,从第一页响应中获取
  160. while True:
  161. print(f"\n正在获取第 {page_num} 页数据...")
  162. result = self.get_major_list(page_num, max_page_size)
  163. # 超星API成功时code=1,失败时code!=1
  164. if result.get("code") != 1:
  165. print(f"获取第 {page_num} 页失败: {result.get('message', '未知错误')}")
  166. break
  167. # 获取分页信息(从extendData中)
  168. extend_data = result.get("extendData", {})
  169. if total_pages is None and extend_data:
  170. total_pages = int(extend_data.get("pages", 0))
  171. total_count = int(extend_data.get("count", 0))
  172. print(f"总页数: {total_pages}, 总条数: {total_count}")
  173. data = result.get("data")
  174. if not data:
  175. print("没有更多数据")
  176. break
  177. # 如果是列表,直接添加
  178. if isinstance(data, list):
  179. all_data.extend(data)
  180. print(f"第 {page_num} 页获取 {len(data)} 条数据,累计 {len(all_data)} 条")
  181. # 如果知道总页数,检查是否已获取完所有页
  182. if total_pages and page_num >= total_pages:
  183. break
  184. # 如果返回的数据少于page_size,说明已经是最后一页
  185. if len(data) < max_page_size:
  186. break
  187. # 如果是字典,可能包含分页信息
  188. elif isinstance(data, dict):
  189. items = data.get("list", data.get("records", []))
  190. if items:
  191. all_data.extend(items)
  192. print(f"第 {page_num} 页获取 {len(items)} 条数据,累计 {len(all_data)} 条")
  193. # 检查是否还有下一页
  194. total = data.get("total", 0)
  195. if total and len(all_data) >= total:
  196. break
  197. else:
  198. break
  199. else:
  200. break
  201. page_num += 1
  202. print(f"\n总共获取 {len(all_data)} 条专业信息")
  203. return all_data
  204. def clear_table(self) -> bool:
  205. """
  206. 清空专业数据表
  207. Returns:
  208. 是否成功
  209. """
  210. try:
  211. connection = pymysql.connect(**DB_CONFIG)
  212. try:
  213. with connection.cursor() as cursor:
  214. table_name = TABLE_MAPPING["major"]
  215. cursor.execute(f"TRUNCATE TABLE `{table_name}`")
  216. connection.commit()
  217. print(f"✓ 已清空表 {table_name}")
  218. return True
  219. finally:
  220. connection.close()
  221. except Exception as e:
  222. print(f"✗ 清空表失败: {e}")
  223. return False
  224. def save_to_database(self, data_list: List[Dict]) -> int:
  225. """
  226. 将专业数据保存到数据库
  227. Args:
  228. data_list: 专业数据列表
  229. Returns:
  230. 成功插入的记录数
  231. """
  232. if not data_list:
  233. print("数据列表为空,跳过保存")
  234. return 0
  235. try:
  236. connection = pymysql.connect(**DB_CONFIG)
  237. try:
  238. current_time = int(time.time() * 1000) # 毫秒时间戳
  239. table_name = TABLE_MAPPING["major"]
  240. with connection.cursor() as cursor:
  241. # 构建插入SQL
  242. insert_sql = f"""
  243. INSERT INTO `{table_name}` (
  244. `inserttime`, `updatetime`,
  245. `zysj_zymc`, `zysj_zybh`, `zysj_ssyx`, `zysj_xbdm`,
  246. `zysj_kbzt`, `zysj_xz`, `zysj_pycc`, `zysj_xzm`,
  247. `zysj_sfdlmc`, `zysj_zyywmc`, `zysj_zyjc`, `zysj_sfkz`,
  248. `zysj_qyyxsx`, `zysj_xxdm`, `zysj_zymldm`, `zysj_pzszny`,
  249. `zysj_sczsny`, `zysj_sfzdzy`, `zysj_zdzyjbmc`, `zysj_zdzyjbm`,
  250. `zysj_zdzylxmc`, `zysj_zdzylxm`, `zysj_sszyqmc`, `zysj_sszyqh`,
  251. `zysj_sjzt`, `zysj_sjztm`, `zysj_sjcjsj`, `dataMigrateUniqueId`
  252. ) VALUES (
  253. %s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
  254. %s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
  255. %s, %s, %s, %s, %s, %s, %s, %s, %s, %s
  256. )
  257. """
  258. success_count = 0
  259. for item in data_list:
  260. try:
  261. cursor.execute(insert_sql, (
  262. current_time, current_time, # inserttime, updatetime
  263. item.get('zysj_zymc'), item.get('zysj_zybh'), item.get('zysj_ssyx'), item.get('zysj_xbdm'),
  264. item.get('zysj_kbzt'), item.get('zysj_xz'), item.get('zysj_pycc'), item.get('zysj_xzm'),
  265. item.get('zysj_sfdlmc'), item.get('zysj_zyywmc'), item.get('zysj_zyjc'), item.get('zysj_sfkz'),
  266. item.get('zysj_qyyxsx'), item.get('zysj_xxdm'), item.get('zysj_zymldm'), item.get('zysj_pzszny'),
  267. item.get('zysj_sczsny'), item.get('zysj_sfzdzy'), item.get('zysj_zdzyjbmc'), item.get('zysj_zdzyjbm'),
  268. item.get('zysj_zdzylxmc'), item.get('zysj_zdzylxm'), item.get('zysj_sszyqmc'), item.get('zysj_sszyqh'),
  269. item.get('zysj_sjzt'), item.get('zysj_sjztm'), item.get('zysj_sjcjsj'), item.get('dataMigrateUniqueId')
  270. ))
  271. success_count += 1
  272. except Exception as e:
  273. print(f"插入单条数据失败: {e}, 数据: {item}")
  274. connection.commit()
  275. print(f"✓ 成功插入 {success_count}/{len(data_list)} 条数据到表 {table_name}")
  276. return success_count
  277. finally:
  278. connection.close()
  279. except Exception as e:
  280. print(f"✗ 保存数据到数据库失败: {e}")
  281. return 0
  282. class MajorSync:
  283. """专业数据同步类 - 从cx_zysj表同步到sys_dept表"""
  284. def __init__(self):
  285. """初始化"""
  286. self.tenant_id = '000000'
  287. def get_valid_majors(self) -> List[Dict[str, Any]]:
  288. """
  289. 从cx_zysj表中获取符合条件的数据
  290. Returns:
  291. 符合条件的专业数据列表
  292. """
  293. try:
  294. connection = pymysql.connect(**DB_CONFIG)
  295. try:
  296. with connection.cursor(pymysql.cursors.DictCursor) as cursor:
  297. # 查询符合条件的数据:zysj_kbzt='是'
  298. sql = """
  299. SELECT
  300. zysj_zymc, zysj_zybh, zysj_ssyx, zysj_xbdm, zysj_kbzt
  301. FROM cx_zysj
  302. WHERE zysj_kbzt = '是'
  303. ORDER BY inserttime ASC
  304. """
  305. cursor.execute(sql)
  306. results = cursor.fetchall()
  307. print(f"✓ 从cx_zysj表中查询到 {len(results)} 条符合条件的数据")
  308. return results
  309. finally:
  310. connection.close()
  311. except Exception as e:
  312. print(f"✗ 查询cx_zysj表失败: {e}")
  313. return []
  314. def find_parent_dept(self, zysj_xbdm: str, zysj_ssyx: str) -> Optional[Dict[str, Any]]:
  315. """
  316. 查找父部门信息
  317. 通过 dept_category='xy-' + zysj_xbdm AND dept_name=zysj_ssyx 找到父部门
  318. Args:
  319. zysj_xbdm: 系部代码
  320. zysj_ssyx: 所属院系名称
  321. Returns:
  322. 父部门信息字典,包含dept_id和ancestors,如果未找到则返回None
  323. """
  324. if not zysj_xbdm or not zysj_ssyx:
  325. return None
  326. try:
  327. connection = pymysql.connect(**DB_CONFIG)
  328. try:
  329. with connection.cursor(pymysql.cursors.DictCursor) as cursor:
  330. dept_category = f"xy-{zysj_xbdm}"
  331. sql = """
  332. SELECT dept_id, ancestors
  333. FROM sys_dept
  334. WHERE dept_category = %s AND dept_name = %s
  335. LIMIT 1
  336. """
  337. cursor.execute(sql, (dept_category, zysj_ssyx))
  338. result = cursor.fetchone()
  339. return result
  340. finally:
  341. connection.close()
  342. except Exception as e:
  343. print(f"✗ 查找父部门失败: {e}")
  344. return None
  345. def check_major_exists(self, dept_name: str, dept_category: str) -> bool:
  346. """
  347. 检查专业是否已存在
  348. Args:
  349. dept_name: 专业名称
  350. dept_category: 专业类别编码
  351. Returns:
  352. 是否存在
  353. """
  354. try:
  355. connection = pymysql.connect(**DB_CONFIG)
  356. try:
  357. with connection.cursor() as cursor:
  358. sql = """
  359. SELECT COUNT(*) as count
  360. FROM sys_dept
  361. WHERE dept_name = %s AND dept_category = %s
  362. """
  363. cursor.execute(sql, (dept_name, dept_category))
  364. result = cursor.fetchone()
  365. return result[0] > 0
  366. finally:
  367. connection.close()
  368. except Exception as e:
  369. print(f"✗ 检查专业是否存在失败: {e}")
  370. return False
  371. def get_max_order_num(self, parent_id: int) -> int:
  372. """
  373. 获取指定父节点下的最大order_num
  374. Args:
  375. parent_id: 父部门ID
  376. Returns:
  377. 最大order_num,如果没有数据则返回0
  378. """
  379. try:
  380. connection = pymysql.connect(**DB_CONFIG)
  381. try:
  382. with connection.cursor() as cursor:
  383. sql = """
  384. SELECT MAX(order_num) as max_order
  385. FROM sys_dept
  386. WHERE parent_id = %s
  387. """
  388. cursor.execute(sql, (parent_id,))
  389. result = cursor.fetchone()
  390. return result[0] if result[0] is not None else 0
  391. finally:
  392. connection.close()
  393. except Exception as e:
  394. print(f"✗ 获取最大order_num失败: {e}")
  395. return 0
  396. def generate_dept_id(self, base_timestamp: str, index: int) -> int:
  397. """
  398. 生成dept_id
  399. 格式:当前时间戳(%Y%m%d%H%M%S)+ 0001开始递增
  400. Args:
  401. base_timestamp: 基础时间戳字符串(格式:%Y%m%d%H%M%S)
  402. index: 序号(从1开始)
  403. Returns:
  404. dept_id
  405. """
  406. # 序号格式化为4位数字(0001, 0002, ...)
  407. sequence = str(index).zfill(4)
  408. # 拼接:时间戳 + 序号
  409. dept_id_str = base_timestamp + sequence
  410. return int(dept_id_str)
  411. def sync_majors(self) -> Dict[str, Any]:
  412. """
  413. 同步专业数据到sys_dept表
  414. Returns:
  415. 同步结果统计(包含成功、跳过、失败的专业编号列表)
  416. """
  417. # 获取符合条件的数据
  418. valid_majors = self.get_valid_majors()
  419. if not valid_majors:
  420. print("没有符合条件的数据需要同步")
  421. return {
  422. "total": 0,
  423. "inserted": 0,
  424. "skipped": 0,
  425. "failed": 0,
  426. "inserted_zybh": [],
  427. "skipped_zybh": [],
  428. "failed_zybh": []
  429. }
  430. # 生成基础时间戳(用于生成dept_id)
  431. base_timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
  432. # 统计信息
  433. stats = {
  434. "total": len(valid_majors),
  435. "inserted": 0,
  436. "skipped": 0,
  437. "failed": 0,
  438. "inserted_zybh": [], # 成功插入的专业编号列表
  439. "skipped_zybh": [], # 跳过的专业编号列表
  440. "failed_zybh": [] # 失败的专业编号列表
  441. }
  442. # 当前时间
  443. current_time = datetime.now()
  444. # 用于生成dept_id的序号计数器(只对实际插入的数据计数)
  445. insert_index = 0
  446. # 按父部门分组,以便管理order_num
  447. parent_order_map = {} # {parent_id: current_order_num}
  448. try:
  449. connection = pymysql.connect(**DB_CONFIG)
  450. try:
  451. with connection.cursor() as cursor:
  452. # 准备插入SQL
  453. insert_sql = """
  454. INSERT INTO sys_dept (
  455. dept_id, tenant_id, parent_id, ancestors, dept_name, dept_category,
  456. order_num, leader, phone, email, status, del_flag,
  457. create_dept, create_by, create_time, update_by, update_time
  458. ) VALUES (
  459. %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s
  460. )
  461. """
  462. # 遍历数据,逐个处理
  463. for major in valid_majors:
  464. zysj_zymc = major.get('zysj_zymc')
  465. zysj_zybh = major.get('zysj_zybh')
  466. zysj_ssyx = major.get('zysj_ssyx')
  467. zysj_xbdm = major.get('zysj_xbdm')
  468. # 验证数据
  469. if not zysj_zymc:
  470. print(f"⚠ 跳过:专业名称为空 (zysj_zybh: {zysj_zybh})")
  471. stats["skipped"] += 1
  472. if zysj_zybh:
  473. stats["skipped_zybh"].append(zysj_zybh)
  474. continue
  475. if not zysj_zybh:
  476. print(f"⚠ 跳过:专业编号为空 (zysj_zymc: {zysj_zymc})")
  477. stats["skipped"] += 1
  478. # 专业编号为空,无法记录
  479. continue
  480. if not zysj_ssyx:
  481. print(f"⚠ 跳过:所属院系为空 (zysj_zymc: {zysj_zymc}, zysj_zybh: {zysj_zybh})")
  482. stats["skipped"] += 1
  483. stats["skipped_zybh"].append(zysj_zybh)
  484. continue
  485. if not zysj_xbdm:
  486. print(f"⚠ 跳过:系部代码为空 (zysj_zymc: {zysj_zymc}, zysj_zybh: {zysj_zybh})")
  487. stats["skipped"] += 1
  488. stats["skipped_zybh"].append(zysj_zybh)
  489. continue
  490. # 检查dept_name长度(sys_dept表中dept_name是varchar(30))
  491. if len(zysj_zymc) > 30:
  492. print(f"⚠ 警告:专业名称超过30字符,将被截断: {zysj_zymc} (长度: {len(zysj_zymc)})")
  493. zysj_zymc = zysj_zymc[:30]
  494. # 构建dept_category
  495. dept_category = f"zy-{zysj_zybh}"
  496. # 检查是否已存在
  497. if self.check_major_exists(zysj_zymc, dept_category):
  498. print(f"⏭ 跳过已存在的专业: {zysj_zymc} (dept_category: {dept_category})")
  499. stats["skipped"] += 1
  500. stats["skipped_zybh"].append(zysj_zybh)
  501. continue
  502. # 查找父部门
  503. parent_dept = self.find_parent_dept(zysj_xbdm, zysj_ssyx)
  504. if not parent_dept:
  505. print(f"⚠ 跳过:未找到父部门 (zysj_ssyx: {zysj_ssyx}, zysj_xbdm: {zysj_xbdm}, zysj_zymc: {zysj_zymc})")
  506. stats["skipped"] += 1
  507. stats["skipped_zybh"].append(zysj_zybh)
  508. continue
  509. parent_id = parent_dept['dept_id']
  510. parent_ancestors = parent_dept['ancestors']
  511. # 构建ancestors:父部门的ancestors + 父部门的dept_id
  512. if parent_ancestors:
  513. ancestors = f"{parent_ancestors},{parent_id}"
  514. else:
  515. ancestors = str(parent_id)
  516. # 获取或初始化该父部门下的order_num
  517. if parent_id not in parent_order_map:
  518. max_order_num = self.get_max_order_num(parent_id)
  519. parent_order_map[parent_id] = max_order_num
  520. # 递增order_num
  521. parent_order_map[parent_id] += 1
  522. current_order_num = parent_order_map[parent_id]
  523. # 递增插入序号(只对实际插入的数据计数)
  524. insert_index += 1
  525. # 生成dept_id
  526. dept_id = self.generate_dept_id(base_timestamp, insert_index)
  527. # 准备插入数据
  528. try:
  529. cursor.execute(insert_sql, (
  530. dept_id, # dept_id
  531. self.tenant_id, # tenant_id
  532. parent_id, # parent_id
  533. ancestors, # ancestors
  534. zysj_zymc, # dept_name
  535. dept_category, # dept_category
  536. current_order_num, # order_num
  537. None, # leader
  538. None, # phone
  539. None, # email
  540. '0', # status
  541. '0', # del_flag
  542. 0, # create_dept
  543. 1, # create_by
  544. current_time, # create_time
  545. 1, # update_by
  546. current_time # update_time
  547. ))
  548. stats["inserted"] += 1
  549. stats["inserted_zybh"].append(zysj_zybh)
  550. print(f"✓ 插入专业: {zysj_zymc} (dept_id: {dept_id}, dept_category: {dept_category}, parent_id: {parent_id}, order_num: {current_order_num})")
  551. except Exception as e:
  552. stats["failed"] += 1
  553. stats["failed_zybh"].append(zysj_zybh)
  554. print(f"✗ 插入专业失败: {zysj_zymc}, 错误: {e}")
  555. # 提交事务
  556. connection.commit()
  557. print(f"\n✓ 事务提交成功")
  558. finally:
  559. connection.close()
  560. except Exception as e:
  561. print(f"✗ 同步过程发生错误: {e}")
  562. stats["failed"] = stats["total"] - stats["inserted"] - stats["skipped"]
  563. return stats
  564. def print_statistics(self, stats: Dict[str, Any]):
  565. """
  566. 打印统计信息
  567. Args:
  568. stats: 统计信息字典(包含专业编号列表)
  569. """
  570. print("\n" + "=" * 60)
  571. print("同步统计信息")
  572. print("=" * 60)
  573. print(f"总数: {stats['total']}")
  574. print(f"成功插入: {stats['inserted']}")
  575. print(f"跳过: {stats['skipped']}")
  576. print(f"失败: {stats['failed']}")
  577. print("=" * 60)
  578. # 输出专业编号列表(逗号分隔)
  579. if stats.get('inserted_zybh'):
  580. inserted_zybh_str = ','.join(stats['inserted_zybh'])
  581. print(f"\n成功插入的专业编号: {inserted_zybh_str}")
  582. if stats.get('skipped_zybh'):
  583. skipped_zybh_str = ','.join(stats['skipped_zybh'])
  584. print(f"跳过的专业编号: {skipped_zybh_str}")
  585. if stats.get('failed_zybh'):
  586. failed_zybh_str = ','.join(stats['failed_zybh'])
  587. print(f"失败的专业编号: {failed_zybh_str}")
  588. print("=" * 60)
  589. def main():
  590. """主函数 - 示例用法"""
  591. # API配置信息
  592. APP_ID = "36c1e58c-7f0a-4567-8ea8-93c7acadbbde"
  593. APP_KEY = "dyjs_zyxx"
  594. FID = "19628" # 单位ID
  595. # 创建API客户端
  596. api = ChaoxingMajorAPI(app_id=APP_ID, app_key=APP_KEY, fid=FID)
  597. # 获取所有数据并保存到数据库
  598. print("=" * 60)
  599. print("获取所有专业信息并保存到数据库")
  600. print("=" * 60)
  601. # 步骤1: 清空表
  602. print("\n步骤1: 清空专业数据表...")
  603. api.clear_table()
  604. # 步骤2: 获取所有数据
  605. print("\n步骤2: 获取所有专业信息(自动分页)...")
  606. all_majors = api.get_all_majors(max_page_size=500)
  607. # 步骤3: 保存到数据库
  608. if all_majors:
  609. print(f"\n步骤3: 保存数据到数据库...")
  610. saved_count = api.save_to_database(all_majors)
  611. print(f"\n✓ 完成!共获取 {len(all_majors)} 条数据,成功保存 {saved_count} 条")
  612. print(f"\n前3条数据示例:")
  613. for i, major in enumerate(all_majors[:3], 1):
  614. print(f"{i}. {major}")
  615. else:
  616. print("\n✗ 未获取到数据")
  617. # ========== 第二阶段:从cx_zysj表同步到sys_dept表 ==========
  618. print("\n" + "=" * 60)
  619. print("第二阶段:从cx_zysj表同步到sys_dept表")
  620. print("=" * 60)
  621. print(f"筛选条件: zysj_kbzt='是'")
  622. print(f"判断是否已存在: dept_category='zy-' + zysj_zybh AND dept_name=zysj_zymc")
  623. print(f"父部门查找: dept_category='xy-' + zysj_xbdm AND dept_name=zysj_ssyx")
  624. print("=" * 60)
  625. # 创建同步实例
  626. sync = MajorSync()
  627. # 执行同步
  628. stats = sync.sync_majors()
  629. # 打印统计信息
  630. sync.print_statistics(stats)
  631. if __name__ == "__main__":
  632. main()