routes.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  1. from flask import request, jsonify
  2. from app.api.data_model import bp
  3. from app.models.result import success, failed
  4. from app.api.graph.routes import MyEncoder
  5. from app.core.data_model import model as model_functions
  6. import json
  7. import logging
  8. # Configure logger
  9. logger = logging.getLogger(__name__)
  10. # 2024.09.11 数据模型血缘关系(传入数据资源id)
  11. @bp.route('/model/data/relation', methods=['POST'])
  12. def data_model_relation():
  13. try:
  14. # 传入请求参数
  15. receiver = request.get_json()
  16. resource_ids = receiver['id'] # 给定一个数据资源的id
  17. response_data = model_functions.handle_model_relation(resource_ids)
  18. res = success(response_data, "success")
  19. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  20. except Exception as e:
  21. res = failed({}, {"error": f"{e}"})
  22. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  23. # 传入一个数据资源的id,返回多个有血缘关系的数据资源
  24. @bp.route('/model/relatives/relation', methods=['POST'])
  25. def data_relatives_relation():
  26. try:
  27. # 传入请求参数
  28. receiver = request.get_json()
  29. page = int(receiver.get('current', 1))
  30. page_size = int(receiver.get('size', 10))
  31. id = receiver['id']
  32. name_zh_filter = receiver.get('name_zh', None)
  33. category = receiver.get('category', None)
  34. time = receiver.get('time', None)
  35. # 计算跳过的记录的数量
  36. skip_count = (page - 1) * page_size
  37. data, total = model_functions.model_resource_list(skip_count, page_size, name_zh_filter, id, category, time)
  38. response_data = {'records': data, 'total': total, 'size': page_size, 'current': page}
  39. res = success(response_data, "success")
  40. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  41. except Exception as e:
  42. res = failed({}, {"error": f"{e}"})
  43. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  44. # DDL数据模型保存
  45. @bp.route('/data/model/save', methods=['POST'])
  46. def data_model_save():
  47. # 传入请求参数
  48. receiver = request.get_json()
  49. data_model = receiver['name_zh']
  50. id_list = receiver['id_list']
  51. data_source = receiver.get('data_source') # 获取data_source节点ID
  52. # resource_id和meta_id构成json格式
  53. result = json.dumps(id_list, ensure_ascii=False)
  54. try:
  55. # 从DDL中选取保存数据模型(支持data_source参数)
  56. result_list = [receiver['name_en']]
  57. id, data_model_node = model_functions.handle_data_model(data_model, result_list, result, receiver)
  58. model_functions.handle_no_meta_data_model(id_list, receiver, data_model_node)
  59. model_functions.calculate_model_level(id)
  60. # 查询节点的实际属性(data_model_node 可能只是整数ID)
  61. from app.services.neo4j_driver import neo4j_driver
  62. with neo4j_driver.get_session() as session:
  63. node_query = """
  64. MATCH (n:DataModel) WHERE id(n) = $node_id
  65. RETURN n.name_zh as name_zh, n.name_en as name_en, n.description as description,
  66. n.category as category, n.create_time as create_time, n.level as level,
  67. n.tag as tag, n.leader as leader, n.origin as origin, n.frequency as frequency,
  68. n.organization as organization, n.data_sensitivity as data_sensitivity, n.status as status
  69. """
  70. node_result = session.run(node_query, node_id=int(id))
  71. node_record = node_result.single()
  72. # 构建响应数据 - data_model包装格式
  73. response_data = {
  74. "data_model": {
  75. "id": id,
  76. "name_zh": node_record['name_zh'] if node_record else None,
  77. "name_en": node_record['name_en'] if node_record else None,
  78. "description": node_record['description'] if node_record else None,
  79. "category": node_record['category'] if node_record else None,
  80. "create_time": node_record['create_time'] if node_record else None,
  81. "level": node_record['level'] if node_record else None,
  82. "tag": node_record['tag'] if node_record else None,
  83. "leader": node_record['leader'] if node_record else None,
  84. "origin": node_record['origin'] if node_record else None,
  85. "frequency": node_record['frequency'] if node_record else None,
  86. "organization": node_record['organization'] if node_record else None,
  87. "data_sensitivity": node_record['data_sensitivity'] if node_record else None,
  88. "status": node_record['status'] if node_record else None,
  89. "data_source": data_source # 数据源节点ID
  90. }
  91. }
  92. res = success(response_data, "success")
  93. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  94. except Exception as e:
  95. res = failed({}, {"error": f"{e}"})
  96. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  97. # 新建数据模型请求接口(从数据资源中选取)
  98. # @bp.route('/model/data/search', methods=['POST'])
  99. @bp.route('/data/search', methods=['POST'])
  100. def data_model_search():
  101. # 传入请求参数
  102. receiver = request.get_json()
  103. data_model = receiver['name_zh']
  104. id_list = receiver['id_list']
  105. data_source = receiver.get('data_source') # 获取data_source节点ID
  106. # resource_id和meta_id构成json格式
  107. result = json.dumps(id_list, ensure_ascii=False)
  108. try:
  109. # 从数据资源中选取保存数据模型(支持data_source参数)
  110. from app.core.meta_data import translate_and_parse
  111. result_list = translate_and_parse(data_model)
  112. id, data_model_node = model_functions.handle_data_model(data_model, result_list, result, receiver)
  113. if id_list:
  114. model_functions.resource_handle_meta_data_model(id_list, id)
  115. model_functions.calculate_model_level(id)
  116. # 查询节点的实际属性(data_model_node 可能只是整数ID)
  117. from app.services.neo4j_driver import neo4j_driver
  118. with neo4j_driver.get_session() as session:
  119. node_query = """
  120. MATCH (n:DataModel) WHERE id(n) = $node_id
  121. RETURN n.name_zh as name_zh, n.name_en as name_en, n.description as description,
  122. n.category as category, n.create_time as create_time, n.level as level,
  123. n.tag as tag, n.leader as leader, n.origin as origin, n.frequency as frequency,
  124. n.organization as organization, n.data_sensitivity as data_sensitivity, n.status as status
  125. """
  126. node_result = session.run(node_query, node_id=int(id))
  127. node_record = node_result.single()
  128. # 构建响应数据 - data_model包装格式
  129. response_data = {
  130. "data_model": {
  131. "id": id,
  132. "name_zh": node_record['name_zh'] if node_record else None,
  133. "name_en": node_record['name_en'] if node_record else None,
  134. "description": node_record['description'] if node_record else None,
  135. "category": node_record['category'] if node_record else None,
  136. "create_time": node_record['create_time'] if node_record else None,
  137. "level": node_record['level'] if node_record else None,
  138. "tag": node_record['tag'] if node_record else None,
  139. "leader": node_record['leader'] if node_record else None,
  140. "origin": node_record['origin'] if node_record else None,
  141. "frequency": node_record['frequency'] if node_record else None,
  142. "organization": node_record['organization'] if node_record else None,
  143. "data_sensitivity": node_record['data_sensitivity'] if node_record else None,
  144. "status": node_record['status'] if node_record else None,
  145. "data_source": data_source # 数据源节点ID
  146. }
  147. }
  148. res = success(response_data, "success")
  149. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  150. except Exception as e:
  151. res = failed({}, {"error": f"{e}"})
  152. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  153. # 新建数据模型请求接口(从数据模型中选取)
  154. @bp.route('/model/data/model/add', methods=['POST'])
  155. def data_model_model_add():
  156. # 传入请求参数
  157. receiver = request.get_json()
  158. data_model = receiver['name_zh']
  159. id_list = receiver['id_list']
  160. data_source = receiver.get('data_source') # 获取data_source节点ID
  161. # model_id和meta_id构成json格式
  162. result = json.dumps(id_list, ensure_ascii=False)
  163. try:
  164. # 从数据模型中选取保存数据模型
  165. # handle_data_model 已经处理了 data_source 关系创建(支持 int/dict/string 格式)
  166. from app.core.meta_data import translate_and_parse
  167. result_list = translate_and_parse(data_model)
  168. node_id, data_model_node = model_functions.handle_data_model(data_model, result_list, result, receiver)
  169. model_functions.model_handle_meta_data_model(id_list, node_id)
  170. model_functions.calculate_model_level(node_id)
  171. # 查询节点的实际属性(data_model_node 可能只是整数ID)
  172. from app.services.neo4j_driver import neo4j_driver
  173. with neo4j_driver.get_session() as session:
  174. node_query = """
  175. MATCH (n:DataModel) WHERE id(n) = $node_id
  176. RETURN n.name_zh as name_zh, n.name_en as name_en, n.description as description,
  177. n.category as category, n.create_time as create_time, n.level as level,
  178. n.tag as tag, n.leader as leader, n.origin as origin, n.frequency as frequency,
  179. n.organization as organization, n.data_sensitivity as data_sensitivity, n.status as status
  180. """
  181. node_result = session.run(node_query, node_id=int(node_id))
  182. node_record = node_result.single()
  183. # 构建响应数据 - data_model包装格式
  184. response_data = {
  185. "data_model": {
  186. "id": node_id,
  187. "name_zh": node_record['name_zh'] if node_record else None,
  188. "name_en": node_record['name_en'] if node_record else None,
  189. "description": node_record['description'] if node_record else None,
  190. "category": node_record['category'] if node_record else None,
  191. "create_time": node_record['create_time'] if node_record else None,
  192. "level": node_record['level'] if node_record else None,
  193. "tag": node_record['tag'] if node_record else None,
  194. "leader": node_record['leader'] if node_record else None,
  195. "origin": node_record['origin'] if node_record else None,
  196. "frequency": node_record['frequency'] if node_record else None,
  197. "organization": node_record['organization'] if node_record else None,
  198. "data_sensitivity": node_record['data_sensitivity'] if node_record else None,
  199. "status": node_record['status'] if node_record else None,
  200. "data_source": data_source # 数据源节点ID,与name_zh在同一级别
  201. }
  202. }
  203. res = success(response_data, "success")
  204. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  205. except Exception as e:
  206. res = failed({}, {"error": f"{e}"})
  207. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  208. # 数据模型-详情接口
  209. @bp.route('/data/model/detail', methods=['POST'])
  210. def data_model_detail():
  211. try:
  212. # 传入请求参数
  213. receiver = request.get_json()
  214. # 直接使用字符串ID,不做类型转换
  215. id = receiver.get('id')
  216. print(f"Received id from frontend: {id}")
  217. result_data = model_functions.handle_id_model(id)
  218. # handle_id_model 返回的数据格式是 {"data_model": {...}}
  219. # 提取内部的 data_model 数据
  220. model_data = result_data.get("data_model", {})
  221. # 查询关联的数据源信息
  222. from app.services.neo4j_driver import neo4j_driver
  223. data_source_id = None
  224. try:
  225. model_id = int(id)
  226. with neo4j_driver.get_session() as session:
  227. # 查询数据模型关联的数据源节点
  228. ds_cypher = """
  229. MATCH (m:DataModel)-[:COME_FROM]->(ds:DataSource)
  230. WHERE id(m) = $model_id
  231. RETURN id(ds) as ds_id
  232. """
  233. ds_result = session.run(ds_cypher, model_id=model_id)
  234. ds_record = ds_result.single()
  235. if ds_record:
  236. # 如果存在数据源关联,只返回ID
  237. data_source_id = ds_record['ds_id']
  238. logger.info(f"找到数据模型关联的数据源: model_id={model_id}, data_source_id={data_source_id}")
  239. else:
  240. logger.info(f"数据模型未关联数据源: model_id={model_id}")
  241. except Exception as e:
  242. # 数据源查询失败不应该影响主流程
  243. logger.error(f"查询数据源关联失败(不中断主流程): {str(e)}")
  244. # 删除 childrenId 字段(如果存在)
  245. if 'childrenId' in model_data:
  246. del model_data['childrenId']
  247. # 添加 data_source 字段
  248. model_data['data_source'] = data_source_id
  249. # 构建响应数据 - data_model包装格式
  250. response_data = {
  251. "data_model": model_data
  252. }
  253. res = success(response_data, "success")
  254. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  255. except Exception as e:
  256. import traceback
  257. traceback.print_exc()
  258. res = failed({}, {"error": f"{e}"})
  259. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  260. # 删除数据模型
  261. @bp.route('/data/model/delete', methods=['POST'])
  262. def data_model_delete():
  263. try:
  264. # 传入请求参数
  265. receiver = request.get_json()
  266. id = receiver.get('id')
  267. print(f"Deleting data model with id: {id}")
  268. # 首先删除数据模型节点
  269. from app.services.neo4j_driver import neo4j_driver
  270. query = """
  271. MATCH (n:DataModel) where id(n) = $nodeId
  272. detach delete n
  273. """
  274. with neo4j_driver.get_session() as session:
  275. session.run(query, nodeId=id)
  276. res = success({}, "success")
  277. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  278. except Exception as e:
  279. import traceback
  280. traceback.print_exc()
  281. res = failed({}, {"error": f"{e}"})
  282. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  283. # 列表 数据模型查询
  284. @bp.route('/data/model/list', methods=['POST'])
  285. def data_model_list():
  286. try:
  287. # 传入请求参数
  288. receiver = request.get_json()
  289. page = int(receiver.get('current', 1))
  290. page_size = int(receiver.get('size', 10))
  291. name_zh_filter = receiver.get('name_zh', None)
  292. name_en_filter = receiver.get('name_en', None)
  293. category = receiver.get('category', None)
  294. tag = receiver.get('tag', None)
  295. level = receiver.get('level', None)
  296. # 计算跳过的记录的数量
  297. skip_count = (page - 1) * page_size
  298. data, total = model_functions.model_list(skip_count, page_size, name_en_filter,
  299. name_zh_filter, category, tag, level)
  300. response_data = {'records': data, 'total': total, 'size': page_size, 'current': page}
  301. res = success(response_data, "success")
  302. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  303. except Exception as e:
  304. res = failed({}, {"error": f"{e}"})
  305. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  306. # 数据模型的图谱(血缘关系Kinship+影响关系Impact+所有关系all+社区关系community)
  307. @bp.route('/data/model/graph/all', methods=['POST'])
  308. def data_model_graph_all():
  309. try:
  310. # 传入请求参数
  311. receiver = request.get_json()
  312. type = receiver['type'] # kinship/impact/all/community
  313. if type == 'community':
  314. # 社区图谱查询,提取tag参数
  315. tag = receiver.get('tag', None)
  316. result = model_functions.model_community(tag)
  317. else:
  318. # 非社区查询时才提取nodeid和meta参数
  319. nodeid = receiver.get('id')
  320. meta = receiver.get('meta', False) # true/false 是否返回元数据
  321. if type == 'kinship':
  322. result = model_functions.model_kinship_graph(nodeid, meta)
  323. elif type == 'impact':
  324. result = model_functions.model_impact_graph(nodeid, meta)
  325. else:
  326. result = model_functions.model_all_graph(nodeid, meta)
  327. return json.dumps(success(result, "success"), ensure_ascii=False, cls=MyEncoder)
  328. except Exception as e:
  329. return json.dumps(failed({}, str(e)), ensure_ascii=False, cls=MyEncoder)
  330. # 数据模型的列表图谱
  331. @bp.route('/data/model/list/graph', methods=['POST'])
  332. def data_model_list_graph():
  333. try:
  334. # 传入请求参数
  335. receiver = request.get_json()
  336. if not receiver or 'tag' not in receiver:
  337. raise ValueError("Missing 'tag' parameter in request body")
  338. nodeid = receiver['tag']
  339. # 构建查询条件
  340. params = {}
  341. where_clause = ""
  342. if nodeid is not None:
  343. where_clause = "MATCH (n)-[:LABEL]->(la) WHERE id(la) = $nodeId"
  344. params['nodeId'] = nodeid
  345. from app.services.neo4j_driver import neo4j_driver
  346. query = f"""
  347. MATCH (n:DataModel)
  348. OPTIONAL MATCH (n)-[:child]->(child)
  349. {where_clause}
  350. WITH
  351. collect(DISTINCT {{id: toString(id(n)), text: n.name_zh, type: split(labels(n)[0], '_')[1]}}) AS nodes,
  352. collect(DISTINCT {{id: toString(id(child)), text: child.name_zh, type: split(labels(child)[0], '_')[1]}}) AS nodes2,
  353. collect(DISTINCT {{from: toString(id(n)), to: toString(id(child)), text: '下级'}}) AS lines
  354. RETURN nodes + nodes2 AS nodes, lines AS lines
  355. """
  356. with neo4j_driver.get_session() as session:
  357. result = session.run(query, **params)
  358. data = result.data()
  359. if len(data) > 0:
  360. # 过滤掉空节点(即 id 为 null 的节点)
  361. nodes = []
  362. for node in data[0]['nodes']:
  363. if node['id'] != 'null':
  364. nodes.append(node)
  365. lines = data[0]['lines']
  366. response_data = {
  367. 'nodes': nodes,
  368. 'edges': lines
  369. }
  370. return json.dumps(success(response_data, "success"), ensure_ascii=False, cls=MyEncoder)
  371. else:
  372. return json.dumps(success({'nodes': [], 'edges': []}, "No data found"), ensure_ascii=False, cls=MyEncoder)
  373. except Exception as e:
  374. return json.dumps(failed({}, str(e)), ensure_ascii=False, cls=MyEncoder)
  375. # 更新数据模型
  376. @bp.route('/data/model/update', methods=['POST'])
  377. def data_model_update():
  378. try:
  379. # 传入请求参数
  380. receiver = request.get_json()
  381. result = model_functions.data_model_edit(receiver)
  382. return json.dumps(success(result, "success"), ensure_ascii=False, cls=MyEncoder)
  383. except Exception as e:
  384. return json.dumps(failed({}, str(e)), ensure_ascii=False, cls=MyEncoder)
  385. # 数据模型关联元数据搜索
  386. @bp.route('/search', methods=['POST'])
  387. def data_model_metadata_search():
  388. """数据模型关联元数据搜索"""
  389. try:
  390. # 获取分页和筛选参数
  391. page = int(request.json.get('current', 1))
  392. page_size = int(request.json.get('size', 10))
  393. model_id = request.json.get('id')
  394. name_en_filter = request.json.get('name_en')
  395. name_zh_filter = request.json.get('name_zh')
  396. category_filter = request.json.get('category')
  397. tag_filter = request.json.get('tag')
  398. if model_id is None:
  399. return json.dumps(failed({}, "模型ID不能为空"), ensure_ascii=False, cls=MyEncoder)
  400. # 确保传入的ID为整数
  401. try:
  402. model_id = int(model_id)
  403. except (ValueError, TypeError):
  404. return json.dumps(failed({}, f"模型ID必须为整数, 收到的是: {model_id}"), ensure_ascii=False, cls=MyEncoder)
  405. # 记录请求信息
  406. logger.info(f"获取数据模型关联元数据请求,ID: {model_id}")
  407. # 调用业务逻辑查询关联元数据
  408. metadata_list, total_count = model_functions.model_search_list(
  409. model_id,
  410. page,
  411. page_size,
  412. name_en_filter,
  413. name_zh_filter,
  414. category_filter,
  415. tag_filter
  416. )
  417. # 返回结果
  418. response_data = {
  419. "records": metadata_list,
  420. "total": total_count,
  421. "size": page_size,
  422. "current": page
  423. }
  424. res = success(response_data, "success")
  425. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
  426. except Exception as e:
  427. logger.error(f"数据模型关联元数据搜索失败: {str(e)}")
  428. res = failed({}, str(e))
  429. return json.dumps(res, ensure_ascii=False, cls=MyEncoder)