ods_sync.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. """
  4. ODS 层原始表同步任务
  5. 说明:轻量级增量同步,只同步昨天的数据,对 MySQL 友好
  6. """
  7. import datetime
  8. from db.pool import get_mysql_conn, get_doris_conn
  9. from logger.handler import get_logger
  10. logger = get_logger("job.ods_patient_exam_sync")
  11. def run(start_date=None, end_date=None):
  12. """执行 ODS 层原始表同步"""
  13. if start_date is None or end_date is None:
  14. yesterday = datetime.date.today() - datetime.timedelta(days=1)
  15. start_date = end_date = yesterday.strftime("%Y-%m-%d")
  16. logger.info("=" * 60)
  17. logger.info(f"[ODS同步] 开始同步: {start_date}")
  18. logger.info("=" * 60)
  19. # 定义需要同步的表
  20. tables = [
  21. {
  22. "name": "ods_patient_custome",
  23. "source_table": "patient_custome",
  24. "time_field": "update_time",
  25. "sync_mode": "increment", # 增量
  26. "columns": """
  27. id,
  28. patientId,
  29. name,
  30. sex,
  31. birthday,
  32. identity,
  33. mobile,
  34. source,
  35. inviter,
  36. inviter_phone,
  37. customer_optometrist_staff_id,
  38. create_time,
  39. update_time
  40. """
  41. },
  42. {
  43. "name": "ods_clinic_clinic",
  44. "source_table": "clinic_clinic",
  45. "time_field": "create_time",
  46. "sync_mode": "increment",
  47. "columns": """
  48. id,
  49. patient_id,
  50. org_id,
  51. type,
  52. channel,
  53. add_user,
  54. appoint_date,
  55. AppointmentTime_start,
  56. matter,
  57. accept_doctor_id,
  58. accept_optometrist_id,
  59. accept_assistant_id,
  60. come_time,
  61. come_date,
  62. reg_status,
  63. pno,
  64. create_time,
  65. update_time
  66. """
  67. },
  68. {
  69. "name": "ods_exam_examresult",
  70. "source_table": "exam_examresult",
  71. "time_field": "created",
  72. "sync_mode": "increment",
  73. "columns": """
  74. id,
  75. clinic_id,
  76. org_id,
  77. result,
  78. tab,
  79. visual,
  80. exam_id,
  81. created,
  82. create_time,
  83. update_time
  84. """
  85. },
  86. {
  87. "name": "ods_treat_sellgoods",
  88. "source_table": "treat_sellgoods",
  89. "time_field": "create_time",
  90. "sync_mode": "increment",
  91. "columns": """
  92. id,
  93. sell_goods_list_id,
  94. goods_id,
  95. out_time,
  96. out_date,
  97. remark,
  98. create_time,
  99. update_time
  100. """
  101. },
  102. {
  103. "name": "ods_material_goods",
  104. "source_table": "material_goods",
  105. "time_field": None,
  106. "sync_mode": "full", # 全量(数据量小)
  107. "columns": """
  108. id,
  109. name,
  110. uuid_no,
  111. fee_class,
  112. group_id,
  113. create_time,
  114. update_time
  115. """
  116. },
  117. {
  118. "name": "ods_fee_order",
  119. "source_table": "fee_order",
  120. "time_field": "create_time",
  121. "sync_mode": "increment",
  122. "columns": """
  123. id,
  124. patientId,
  125. org_id,
  126. pay_status,
  127. pay_time,
  128. pay_date,
  129. total_amt,
  130. create_time,
  131. update_time
  132. """
  133. },
  134. {
  135. "name": "ods_fee_orderdetail",
  136. "source_table": "fee_orderdetail",
  137. "time_field": "create_time",
  138. "sync_mode": "increment",
  139. "columns": """
  140. id,
  141. order_id,
  142. goods_id,
  143. patient_id,
  144. name,
  145. pay_time,
  146. pay_date,
  147. price,
  148. quantity,
  149. total_amt,
  150. create_time,
  151. update_time
  152. """
  153. },
  154. {
  155. "name": "ods_staff_staff",
  156. "source_table": "staff_staff",
  157. "time_field": None,
  158. "sync_mode": "full", # 全量(数据量小)
  159. "columns": """
  160. id,
  161. user_id,
  162. main_org_id,
  163. name,
  164. mobile,
  165. qywx_userid,
  166. status,
  167. create_time,
  168. update_time
  169. """
  170. },
  171. {
  172. "name": "ods_org_org",
  173. "source_table": "org_org",
  174. "time_field": None,
  175. "sync_mode": "full", # 全量(数据量小)
  176. "columns": """
  177. id,
  178. name,
  179. group_id,
  180. is_effective,
  181. create_time,
  182. update_time
  183. """
  184. },
  185. ]
  186. mysql_conn = get_mysql_conn()
  187. doris_conn = get_doris_conn()
  188. try:
  189. success_count = 0
  190. for table_config in tables:
  191. try:
  192. count = sync_table(
  193. mysql_conn,
  194. doris_conn,
  195. table_config,
  196. start_date,
  197. end_date
  198. )
  199. if count > 0:
  200. success_count += 1
  201. except Exception as e:
  202. logger.error(f"同步 {table_config['name']} 失败: {e}")
  203. import traceback
  204. traceback.print_exc()
  205. logger.info("=" * 60)
  206. logger.info(f"[ODS同步] 同步完成,成功 {success_count}/{len(tables)} 个表")
  207. logger.info("=" * 60)
  208. return success_count
  209. finally:
  210. mysql_conn.close()
  211. doris_conn.close()
  212. def sync_table(mysql_conn, doris_conn, table_config, start_date, end_date):
  213. """同步单个表"""
  214. table_name = table_config["name"]
  215. source_table = table_config["source_table"]
  216. time_field = table_config.get("time_field")
  217. sync_mode = table_config["sync_mode"]
  218. columns = table_config["columns"]
  219. logger.info(f"\n开始同步: {table_name} ({sync_mode})")
  220. mysql_cursor = mysql_conn.cursor()
  221. doris_cursor = doris_conn.cursor()
  222. try:
  223. # 1. 从 MySQL 查询数据
  224. if sync_mode == "increment" and time_field:
  225. # 增量同步:只查询昨天的数据
  226. sql = f"""
  227. SELECT {columns}
  228. FROM {source_table}
  229. WHERE {time_field} >= %s
  230. AND {time_field} < %s
  231. """
  232. params = [f"{start_date} 00:00:00", f"{end_date} 23:59:59"]
  233. else:
  234. # 全量同步
  235. sql = f"SELECT {columns} FROM {source_table}"
  236. params = []
  237. logger.info(f"执行SQL: {sql}")
  238. logger.info(f"参数: {params}")
  239. mysql_cursor.execute(sql, params)
  240. data_list = mysql_cursor.fetchall()
  241. row_count = len(data_list)
  242. logger.info(f"查询到 {row_count} 条记录")
  243. if row_count == 0:
  244. logger.info(f"无新数据,跳过")
  245. return 0
  246. # 2. 构建插入 SQL
  247. # 获取列名
  248. columns_list = [col.strip() for col in columns.split(',')]
  249. # 添加源表时间字段
  250. if sync_mode == "increment" and time_field:
  251. columns_list.extend(["source_create_time", "source_update_time"])
  252. placeholders = ', '.join(['%s'] * len(columns_list))
  253. insert_sql = f"INSERT INTO {table_name} ({', '.join(columns_list)}) VALUES ({placeholders})"
  254. # 3. 准备数据
  255. values_list = []
  256. for row in data_list:
  257. row_dict = dict(row)
  258. # 转换数据
  259. values = []
  260. for col in columns_list:
  261. if col in ["source_create_time", "source_update_time"]:
  262. # 源表时间字段
  263. val = row_dict.get(time_field)
  264. else:
  265. # 普通字段
  266. val = row_dict.get(col)
  267. # 处理日期时间
  268. if val and col.endswith("_time"):
  269. if isinstance(val, str):
  270. pass # 已经是字符串
  271. elif hasattr(val, 'strftime'):
  272. val = val.strftime('%Y-%m-%d %H:%M:%S')
  273. else:
  274. val = None
  275. elif val and col.endswith("_date"):
  276. if isinstance(val, str):
  277. pass # 已经是字符串
  278. elif hasattr(val, 'strftime'):
  279. val = val.strftime('%Y-%m-%d')
  280. else:
  281. val = None
  282. values.append(val)
  283. values_list.append(tuple(values))
  284. # 4. 批量插入 Doris
  285. batch_size = 100
  286. total_inserted = 0
  287. for i in range(0, len(values_list), batch_size):
  288. batch = values_list[i:i+batch_size]
  289. doris_cursor.executemany(insert_sql, batch)
  290. doris_conn.commit()
  291. total_inserted += len(batch)
  292. logger.info(f"已写入 {total_inserted}/{row_count}")
  293. logger.info(f"✓ {table_name} 同步完成: {total_inserted} 条")
  294. return total_inserted
  295. except Exception as e:
  296. logger.error(f"同步 {table_name} 失败: {e}")
  297. raise
  298. finally:
  299. mysql_cursor.close()
  300. doris_cursor.close()