routes.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807
  1. from flask import request, jsonify, send_from_directory, send_file, current_app
  2. from app.api.meta_data import bp
  3. from app.models.result import success, failed
  4. import logging
  5. import json
  6. import io
  7. import os
  8. from minio import Minio
  9. from minio.error import S3Error
  10. from app.services.neo4j_driver import neo4j_driver
  11. from app.core.graph.graph_operations import create_or_get_node, relationship_exists
  12. from app.core.meta_data import (
  13. translate_and_parse,
  14. get_formatted_time,
  15. meta_list,
  16. meta_kinship_graph,
  17. meta_impact_graph,
  18. parse_text,
  19. parse_entity_relation,
  20. handle_txt_graph,
  21. get_file_content,
  22. text_resource_solve,
  23. handle_id_unstructured,
  24. solve_unstructured_data
  25. )
  26. from app.core.system.auth import require_auth
  27. logger = logging.getLogger("app")
  28. def get_minio_client():
  29. """获取 MinIO 客户端实例"""
  30. return Minio(
  31. current_app.config['MINIO_HOST'],
  32. access_key=current_app.config['MINIO_USER'],
  33. secret_key=current_app.config['MINIO_PASSWORD'],
  34. secure=current_app.config['MINIO_SECURE']
  35. )
  36. def get_minio_config():
  37. """获取 MinIO 配置"""
  38. return {
  39. 'bucket_name': current_app.config['BUCKET_NAME'],
  40. 'prefix': current_app.config['PREFIX'],
  41. 'allowed_extensions': current_app.config['ALLOWED_EXTENSIONS']
  42. }
  43. def allowed_file(filename):
  44. """检查文件扩展名是否允许"""
  45. return '.' in filename and filename.rsplit('.', 1)[1].lower() in get_minio_config()['allowed_extensions']
  46. # 元数据列表
  47. @bp.route('/node/list', methods=['POST'])
  48. def meta_node_list():
  49. try:
  50. # 从请求中获取分页参数
  51. page = int(request.json.get('current', 1))
  52. page_size = int(request.json.get('size', 10))
  53. # 获取搜索参数
  54. search = request.json.get('search', '')
  55. en_name_filter = request.json.get('en_name', None)
  56. name_filter = request.json.get('name', None)
  57. category_filter = request.json.get('category', None)
  58. time_filter = request.json.get('time', None)
  59. tag_filter = request.json.get('tag', None)
  60. # 调用核心业务逻辑
  61. result, total_count = meta_list(
  62. page,
  63. page_size,
  64. search,
  65. en_name_filter,
  66. name_filter,
  67. category_filter,
  68. time_filter,
  69. tag_filter
  70. )
  71. # 返回结果
  72. return jsonify(success({
  73. "records": result,
  74. "total": total_count,
  75. "size": page_size,
  76. "current": page
  77. }))
  78. except Exception as e:
  79. logger.error(f"获取元数据列表失败: {str(e)}")
  80. return jsonify(failed(str(e)))
  81. # 元数据图谱
  82. @bp.route('/node/graph', methods=['POST'])
  83. def meta_node_graph():
  84. try:
  85. # 从请求中获取节点ID
  86. node_id = request.json.get('nodeId')
  87. # 调用核心业务逻辑
  88. result = meta_kinship_graph(node_id)
  89. # 返回结果
  90. return jsonify(success(result))
  91. except Exception as e:
  92. logger.error(f"获取元数据图谱失败: {str(e)}")
  93. return jsonify(failed(str(e)))
  94. # 删除元数据
  95. @bp.route('/node/delete', methods=['POST'])
  96. def meta_node_delete():
  97. try:
  98. # 从请求中获取节点ID
  99. node_id = request.json.get('id')
  100. # 删除节点逻辑
  101. with neo4j_driver.get_session() as session:
  102. cypher = "MATCH (n) WHERE id(n) = $node_id DETACH DELETE n"
  103. session.run(cypher, node_id=int(node_id))
  104. # 返回结果
  105. return jsonify(success({}))
  106. except Exception as e:
  107. logger.error(f"删除元数据失败: {str(e)}")
  108. return jsonify(failed(str(e)))
  109. # 编辑元数据
  110. @bp.route('/node/edit', methods=['POST'])
  111. def meta_node_edit():
  112. try:
  113. # 从请求中获取节点ID
  114. node_id = request.json.get('id')
  115. if not node_id:
  116. return jsonify(failed("节点ID不能为空"))
  117. # 获取节点
  118. with neo4j_driver.get_session() as session:
  119. # 查询节点信息
  120. cypher = """
  121. MATCH (n:meta_data)
  122. WHERE id(n) = $node_id
  123. RETURN n
  124. """
  125. result = session.run(cypher, node_id=int(node_id))
  126. node = result.single()
  127. if not node or not node["n"]:
  128. return jsonify(failed("节点不存在"))
  129. # 获取节点数据
  130. node_data = dict(node["n"])
  131. node_data["id"] = node["n"].id
  132. # 获取标签信息
  133. tag_cypher = """
  134. MATCH (n:meta_data)-[:label]->(t:data_label)
  135. WHERE id(n) = $node_id
  136. RETURN t
  137. """
  138. tag_result = session.run(tag_cypher, node_id=int(node_id))
  139. tag = tag_result.single()
  140. # 获取主数据信息
  141. master_data_cypher = """
  142. MATCH (n:meta_data)-[:master_data]->(m:master_data)
  143. WHERE id(n) = $node_id
  144. RETURN m
  145. """
  146. master_data_result = session.run(master_data_cypher, node_id=int(node_id))
  147. master_data = master_data_result.single()
  148. # 构建返回数据
  149. response_data = [{
  150. "master_data": master_data["m"].id if master_data and master_data["m"] else None,
  151. "name": node_data.get("name", ""),
  152. "en_name": node_data.get("en_name", ""),
  153. "time": node_data.get("updateTime", ""),
  154. "status": node_data.get("status", "true") == "true",
  155. "type": node_data.get("type", ""),
  156. "tag": {
  157. "name": tag["t"].get("name", "") if tag and tag["t"] else None,
  158. "id": tag["t"].id if tag and tag["t"] else None
  159. },
  160. "affiliation": node_data.get("affiliation"),
  161. "category": node_data.get("category"),
  162. "alias": node_data.get("alias"),
  163. "describe": node_data.get("describe")
  164. }]
  165. logger.info(f"成功获取元数据节点: ID={node_data['id']}")
  166. return jsonify(success(response_data))
  167. except Exception as e:
  168. logger.error(f"获取元数据节点失败: {str(e)}")
  169. return jsonify(failed(str(e)))
  170. # 增加元数据
  171. @bp.route('/node/add', methods=['POST'])
  172. def meta_node_add():
  173. try:
  174. # 从请求中获取节点信息
  175. node_name = request.json.get('name')
  176. node_type = request.json.get('type')
  177. node_category = request.json.get('category')
  178. node_alias = request.json.get('alias')
  179. node_affiliation = request.json.get('affiliation')
  180. node_tag = request.json.get('tag')
  181. node_desc = request.json.get('describe')
  182. node_status = request.json.get('status', 1)
  183. if not node_name:
  184. return jsonify(failed("节点名称不能为空"))
  185. if not node_type:
  186. return jsonify(failed("节点类型不能为空"))
  187. node_en_name = translate_and_parse(node_name)
  188. # 创建节点
  189. with neo4j_driver.get_session() as session:
  190. cypher = """
  191. MERGE (n:meta_data {name: $name})
  192. ON CREATE SET n.data_type = $type,
  193. n.category = $category,
  194. n.alias = $alias,
  195. n.affiliation = $affiliation,
  196. n.desc = $desc,
  197. n.createTime = $create_time,
  198. n.updateTime = $update_time,
  199. n.status = $status,
  200. n.en_name = $en_name
  201. ON MATCH SET n.data_type = $type,
  202. n.category = $category,
  203. n.alias = $alias,
  204. n.affiliation = $affiliation,
  205. n.desc = $desc,
  206. n.updateTime = $update_time,
  207. n.status = $status,
  208. n.en_name = $en_name
  209. RETURN n
  210. """
  211. create_time = update_time = get_formatted_time()
  212. result = session.run(
  213. cypher,
  214. name=node_name,
  215. type=node_type,
  216. category=node_category,
  217. alias=node_alias,
  218. affiliation=node_affiliation,
  219. desc=node_desc,
  220. create_time=create_time,
  221. update_time=update_time,
  222. status=str(node_status),
  223. en_name=node_en_name
  224. )
  225. node = result.single()
  226. if node and node["n"]:
  227. node_data = dict(node["n"])
  228. node_data["id"] = node["n"].id
  229. # 如果提供了标签ID,创建标签关系
  230. if node_tag:
  231. tag_cypher = """
  232. MATCH (n:meta_data), (t:data_label)
  233. WHERE id(n) = $node_id AND id(t) = $tag_id
  234. MERGE (n)-[r:label]->(t)
  235. RETURN r
  236. """
  237. session.run(tag_cypher, node_id=node["n"].id, tag_id=int(node_tag))
  238. logger.info(f"成功创建或更新元数据节点: ID={node_data['id']}, name={node_name}")
  239. return jsonify(success(node_data))
  240. else:
  241. logger.error(f"创建元数据节点失败: {node_name}")
  242. return jsonify(failed("创建元数据节点失败"))
  243. except Exception as e:
  244. logger.error(f"添加元数据失败: {str(e)}")
  245. return jsonify(failed(str(e)))
  246. # 搜索元数据
  247. @bp.route('/search', methods=['GET'])
  248. def search_metadata_route():
  249. try:
  250. keyword = request.args.get('keyword', '')
  251. if not keyword:
  252. return jsonify(success([]))
  253. cypher = """
  254. MATCH (n:meta_data)
  255. WHERE n.name CONTAINS $keyword
  256. RETURN n LIMIT 100
  257. """
  258. with neo4j_driver.get_session() as session:
  259. result = session.run(cypher, keyword=keyword)
  260. metadata_list = [dict(record["n"]) for record in result]
  261. return jsonify(success(metadata_list))
  262. except Exception as e:
  263. logger.error(f"搜索元数据失败: {str(e)}")
  264. return jsonify(failed(str(e)))
  265. # 全文检索查询
  266. @bp.route('/full/text/query', methods=['POST'])
  267. def full_text_query():
  268. try:
  269. # 获取查询条件
  270. query = request.json.get('query', '')
  271. if not query:
  272. return jsonify(failed("查询条件不能为空"))
  273. # 执行Neo4j全文索引查询
  274. with neo4j_driver.get_session() as session:
  275. cypher = """
  276. CALL db.index.fulltext.queryNodes("meta_dataFulltext", $query)
  277. YIELD node, score
  278. RETURN node, score
  279. ORDER BY score DESC
  280. LIMIT 20
  281. """
  282. result = session.run(cypher, query=query)
  283. # 处理查询结果
  284. search_results = []
  285. for record in result:
  286. node_data = dict(record["node"])
  287. node_data["id"] = record["node"].id
  288. node_data["score"] = record["score"]
  289. search_results.append(node_data)
  290. return jsonify(success(search_results))
  291. except Exception as e:
  292. logger.error(f"全文检索查询失败: {str(e)}")
  293. return jsonify(failed(str(e)))
  294. # 非结构化文本查询
  295. @bp.route('/unstructure/text/query', methods=['POST'])
  296. def unstructure_text_query():
  297. try:
  298. # 获取查询参数
  299. node_id = request.json.get('id')
  300. if not node_id:
  301. return jsonify(failed("节点ID不能为空"))
  302. # 获取节点信息
  303. node_data = handle_id_unstructured(node_id)
  304. if not node_data:
  305. return jsonify(failed("节点不存在"))
  306. # 获取对象路径
  307. object_name = node_data.get('url')
  308. if not object_name:
  309. return jsonify(failed("文档路径不存在"))
  310. # 获取 MinIO 配置
  311. minio_client = get_minio_client()
  312. config = get_minio_config()
  313. bucket_name = config['bucket_name']
  314. # 从MinIO获取文件内容
  315. file_content = get_file_content(minio_client, bucket_name, object_name)
  316. # 解析文本内容
  317. parsed_data = parse_text(file_content)
  318. # 返回结果
  319. result = {
  320. "node": node_data,
  321. "parsed": parsed_data,
  322. "content": file_content[:1000] + "..." if len(file_content) > 1000 else file_content
  323. }
  324. return jsonify(success(result))
  325. except Exception as e:
  326. logger.error(f"非结构化文本查询失败: {str(e)}")
  327. return jsonify(failed(str(e)))
  328. # 文件上传
  329. @bp.route('/resource/upload', methods=['POST'])
  330. def upload_file():
  331. try:
  332. # 检查请求中是否有文件
  333. if 'file' not in request.files:
  334. return jsonify(failed("没有找到上传的文件"))
  335. file = request.files['file']
  336. # 检查文件名
  337. if file.filename == '':
  338. return jsonify(failed("未选择文件"))
  339. # 检查文件类型
  340. if not allowed_file(file.filename):
  341. return jsonify(failed("不支持的文件类型"))
  342. # 获取 MinIO 配置
  343. minio_client = get_minio_client()
  344. config = get_minio_config()
  345. # 上传到MinIO
  346. file_content = file.read()
  347. file_size = len(file_content)
  348. file_type = file.filename.rsplit('.', 1)[1].lower()
  349. # 提取文件名(不包含扩展名)
  350. filename_without_ext = file.filename.rsplit('.', 1)[0]
  351. # 生成紧凑的时间戳 (yyyyMMddHHmmss)
  352. import time
  353. timestamp = time.strftime("%Y%m%d%H%M%S", time.localtime())
  354. # 生成唯一文件名
  355. object_name = f"{config['prefix']}/{filename_without_ext}_{timestamp}.{file_type}"
  356. # 上传文件
  357. minio_client.put_object(
  358. config['bucket_name'],
  359. object_name,
  360. io.BytesIO(file_content),
  361. file_size,
  362. content_type=f"application/{file_type}"
  363. )
  364. # 返回结果
  365. return jsonify(success({
  366. "filename": file.filename,
  367. "size": file_size,
  368. "type": file_type,
  369. "url": object_name
  370. }))
  371. except Exception as e:
  372. logger.error(f"文件上传失败: {str(e)}")
  373. return jsonify(failed(str(e)))
  374. # 文件下载显示
  375. @bp.route('/resource/display', methods=['POST'])
  376. def upload_file_display():
  377. response = None
  378. try:
  379. object_name = request.json.get('url')
  380. if not object_name:
  381. return jsonify(failed("文件路径不能为空"))
  382. # 获取 MinIO 配置
  383. minio_client = get_minio_client()
  384. config = get_minio_config()
  385. # 获取文件内容
  386. response = minio_client.get_object(config['bucket_name'], object_name)
  387. file_data = response.read()
  388. # 获取文件名
  389. file_name = object_name.split('/')[-1]
  390. # 确定文件类型
  391. file_extension = file_name.split('.')[-1].lower()
  392. # 为不同文件类型设置合适的MIME类型
  393. mime_types = {
  394. 'pdf': 'application/pdf',
  395. 'doc': 'application/msword',
  396. 'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
  397. 'xls': 'application/vnd.ms-excel',
  398. 'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  399. 'txt': 'text/plain',
  400. 'csv': 'text/csv'
  401. }
  402. content_type = mime_types.get(file_extension, 'application/octet-stream')
  403. # 返回结果
  404. return jsonify(success({
  405. "filename": file_name,
  406. "type": file_extension,
  407. "contentType": content_type,
  408. "size": len(file_data),
  409. "url": f"/api/meta/resource/download?url={object_name}"
  410. }))
  411. except S3Error as e:
  412. logger.error(f"MinIO操作失败: {str(e)}")
  413. return jsonify(failed(f"文件访问失败: {str(e)}"))
  414. except Exception as e:
  415. logger.error(f"文件显示信息获取失败: {str(e)}")
  416. return jsonify(failed(str(e)))
  417. finally:
  418. if response:
  419. response.close()
  420. response.release_conn()
  421. # 文件下载接口
  422. @bp.route('/resource/download', methods=['GET'])
  423. def download_file():
  424. response = None
  425. try:
  426. object_name = request.args.get('url')
  427. if not object_name:
  428. return jsonify(failed("文件路径不能为空"))
  429. # URL解码,处理特殊字符
  430. import urllib.parse
  431. object_name = urllib.parse.unquote(object_name)
  432. # 记录下载请求信息,便于调试
  433. logger.info(f"下载文件请求: {object_name}")
  434. # 获取 MinIO 配置
  435. minio_client = get_minio_client()
  436. config = get_minio_config()
  437. # 获取文件
  438. try:
  439. response = minio_client.get_object(config['bucket_name'], object_name)
  440. file_data = response.read()
  441. except S3Error as e:
  442. logger.error(f"MinIO获取文件失败: {str(e)}")
  443. return jsonify(failed(f"文件获取失败: {str(e)}"))
  444. # 获取文件名,并处理特殊字符
  445. file_name = object_name.split('/')[-1]
  446. # 直接从内存返回文件,不创建临时文件
  447. file_stream = io.BytesIO(file_data)
  448. # 返回文件
  449. return send_file(
  450. file_stream,
  451. as_attachment=True,
  452. download_name=file_name,
  453. mimetype="application/octet-stream"
  454. )
  455. except Exception as e:
  456. logger.error(f"文件下载失败: {str(e)}")
  457. return jsonify(failed(str(e)))
  458. finally:
  459. if response:
  460. response.close()
  461. response.release_conn()
  462. # 文本资源翻译
  463. @bp.route('/resource/translate', methods=['POST'])
  464. def text_resource_translate():
  465. try:
  466. # 获取参数
  467. name = request.json.get('name', '')
  468. keyword = request.json.get('keyword', '')
  469. if not name:
  470. return jsonify(failed("名称不能为空"))
  471. # 调用资源处理逻辑
  472. result = text_resource_solve(None, name, keyword)
  473. return jsonify(success(result))
  474. except Exception as e:
  475. logger.error(f"文本资源翻译失败: {str(e)}")
  476. return jsonify(failed(str(e)))
  477. # 创建文本资源节点
  478. @bp.route('/resource/node', methods=['POST'])
  479. def text_resource_node():
  480. try:
  481. # 获取参数
  482. name = request.json.get('name', '')
  483. en_name = request.json.get('en_name', '')
  484. keywords = request.json.get('keywords', [])
  485. keywords_en = request.json.get('keywords_en', [])
  486. object_name = request.json.get('url', '')
  487. if not name or not en_name or not object_name:
  488. return jsonify(failed("参数不完整"))
  489. # 创建节点
  490. with neo4j_driver.get_session() as session:
  491. # 创建资源节点
  492. cypher = """
  493. CREATE (n:meta_data {
  494. name: $name,
  495. en_name: $en_name,
  496. keywords: $keywords,
  497. keywords_en: $keywords_en,
  498. url: $object_name,
  499. createTime: $create_time,
  500. updateTime: $update_time
  501. })
  502. RETURN n
  503. """
  504. create_time = update_time = get_formatted_time()
  505. result = session.run(
  506. cypher,
  507. name=name,
  508. en_name=en_name,
  509. keywords=keywords,
  510. keywords_en=keywords_en,
  511. object_name=object_name,
  512. create_time=create_time,
  513. update_time=update_time
  514. )
  515. node = result.single()["n"]
  516. # 为每个关键词创建标签节点并关联
  517. for i, keyword in enumerate(keywords):
  518. if keyword:
  519. # 创建标签节点
  520. tag_cypher = """
  521. MERGE (t:Tag {name: $name})
  522. ON CREATE SET t.en_name = $en_name, t.createTime = $create_time
  523. RETURN t
  524. """
  525. tag_result = session.run(
  526. tag_cypher,
  527. name=keyword,
  528. en_name=keywords_en[i] if i < len(keywords_en) else "",
  529. create_time=create_time
  530. )
  531. tag_node = tag_result.single()["t"]
  532. # 创建关系
  533. rel_cypher = """
  534. MATCH (n), (t)
  535. WHERE id(n) = $node_id AND id(t) = $tag_id
  536. CREATE (n)-[r:HAS_TAG]->(t)
  537. RETURN r
  538. """
  539. session.run(
  540. rel_cypher,
  541. node_id=node.id,
  542. tag_id=tag_node.id
  543. )
  544. # 返回创建的节点
  545. return jsonify(success(dict(node)))
  546. except Exception as e:
  547. logger.error(f"创建文本资源节点失败: {str(e)}")
  548. return jsonify(failed(str(e)))
  549. # 处理非结构化数据
  550. @bp.route('/unstructured/process', methods=['POST'])
  551. def processing_unstructured_data():
  552. try:
  553. # 获取参数
  554. node_id = request.json.get('id')
  555. if not node_id:
  556. return jsonify(failed("节点ID不能为空"))
  557. # 获取 MinIO 配置
  558. minio_client = get_minio_client()
  559. config = get_minio_config()
  560. prefix = config['prefix']
  561. # 调用处理逻辑
  562. result = solve_unstructured_data(node_id, minio_client, prefix)
  563. if result:
  564. return jsonify(success({"message": "处理成功"}))
  565. else:
  566. return jsonify(failed("处理失败"))
  567. except Exception as e:
  568. logger.error(f"处理非结构化数据失败: {str(e)}")
  569. return jsonify(failed(str(e)))
  570. # 创建文本图谱
  571. @bp.route('/text/graph', methods=['POST'])
  572. def create_text_graph():
  573. try:
  574. # 获取参数
  575. node_id = request.json.get('id')
  576. entity = request.json.get('entity')
  577. entity_en = request.json.get('entity_en')
  578. if not all([node_id, entity, entity_en]):
  579. return jsonify(failed("参数不完整"))
  580. # 创建图谱
  581. result = handle_txt_graph(node_id, entity, entity_en)
  582. if result:
  583. return jsonify(success({"message": "图谱创建成功"}))
  584. else:
  585. return jsonify(failed("图谱创建失败"))
  586. except Exception as e:
  587. logger.error(f"创建文本图谱失败: {str(e)}")
  588. return jsonify(failed(str(e)))
  589. @bp.route('/config', methods=['GET'])
  590. @require_auth
  591. def get_meta_config():
  592. """获取元数据配置信息"""
  593. config = get_minio_config()
  594. return jsonify({
  595. 'bucket_name': config['bucket_name'],
  596. 'prefix': config['prefix'],
  597. 'allowed_extensions': list(config['allowed_extensions'])
  598. })
  599. # 更新元数据
  600. @bp.route('/node/update', methods=['POST'])
  601. def meta_node_update():
  602. try:
  603. # 从请求中获取节点ID和更新数据
  604. node_id = request.json.get('id')
  605. if not node_id:
  606. return jsonify(failed("节点ID不能为空"))
  607. # 更新节点
  608. with neo4j_driver.get_session() as session:
  609. # 检查节点是否存在并获取当前值
  610. check_cypher = """
  611. MATCH (n:meta_data)
  612. WHERE id(n) = $node_id
  613. RETURN n
  614. """
  615. result = session.run(check_cypher, node_id=int(node_id))
  616. node = result.single()
  617. if not node or not node["n"]:
  618. return jsonify(failed("节点不存在"))
  619. # 获取当前节点的所有属性
  620. current_node = node["n"]
  621. current_properties = dict(current_node)
  622. # 构建更新语句,只更新提供的属性
  623. update_cypher = """
  624. MATCH (n:meta_data)
  625. WHERE id(n) = $node_id
  626. SET n.updateTime = $update_time
  627. """
  628. # 准备更新参数
  629. update_params = {
  630. 'node_id': int(node_id),
  631. 'update_time': get_formatted_time()
  632. }
  633. # 处理每个可能的更新字段
  634. fields_to_update = {
  635. 'name': request.json.get('name'),
  636. 'category': request.json.get('category'),
  637. 'alias': request.json.get('alias'),
  638. 'affiliation': request.json.get('affiliation'),
  639. 'type': request.json.get('type'),
  640. 'describe': request.json.get('describe'),
  641. 'status': request.json.get('status')
  642. }
  643. # 只更新提供了新值的字段
  644. for field, new_value in fields_to_update.items():
  645. if new_value is not None:
  646. update_cypher += f", n.{field} = ${field}\n"
  647. update_params[field] = new_value
  648. else:
  649. # 如果字段没有提供新值,使用当前值
  650. update_params[field] = current_properties.get(field)
  651. update_cypher += f", n.{field} = ${field}\n"
  652. # 处理英文名称
  653. if request.json.get('name'):
  654. update_cypher += ", n.en_name = $en_name\n"
  655. update_params['en_name'] = translate_and_parse(request.json.get('name'))
  656. else:
  657. update_cypher += ", n.en_name = $en_name\n"
  658. update_params['en_name'] = current_properties.get('en_name')
  659. update_cypher += "RETURN n"
  660. result = session.run(update_cypher, **update_params)
  661. updated_node = result.single()
  662. if updated_node and updated_node["n"]:
  663. node_data = dict(updated_node["n"])
  664. node_data["id"] = updated_node["n"].id
  665. # 如果更新了标签,处理标签关系
  666. tag = request.json.get('tag')
  667. if tag is not None:
  668. # 先删除现有标签关系
  669. delete_tag_cypher = """
  670. MATCH (n:meta_data)-[r:label]->(t:data_label)
  671. WHERE id(n) = $node_id
  672. DELETE r
  673. """
  674. session.run(delete_tag_cypher, node_id=int(node_id))
  675. # 创建新的标签关系
  676. if tag and isinstance(tag, dict) and 'id' in tag:
  677. create_tag_cypher = """
  678. MATCH (n:meta_data), (t:data_label)
  679. WHERE id(n) = $node_id AND id(t) = $tag_id
  680. MERGE (n)-[r:label]->(t)
  681. RETURN r
  682. """
  683. session.run(create_tag_cypher, node_id=int(node_id), tag_id=int(tag['id']))
  684. logger.info(f"成功更新元数据节点: ID={node_data['id']}")
  685. return jsonify(success(node_data))
  686. else:
  687. logger.error(f"更新元数据节点失败: ID={node_id}")
  688. return jsonify(failed("更新元数据节点失败"))
  689. except Exception as e:
  690. logger.error(f"更新元数据失败: {str(e)}")
  691. return jsonify(failed(str(e)))