routes.py 21 KB

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