routes.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
  1. from flask import request, jsonify, send_from_directory, send_file
  2. from app.api.meta_data import bp
  3. from app.models.result import success, failed
  4. from app.config.config import Config
  5. import logging
  6. import json
  7. import io
  8. import os
  9. from minio import Minio
  10. from minio.error import S3Error
  11. from app.services.neo4j_driver import neo4j_driver
  12. from app.services.package_function import create_or_get_node, relationship_exists
  13. from app.core.meta_data import (
  14. translate_and_parse,
  15. get_formatted_time,
  16. meta_list,
  17. meta_kinship_graph,
  18. meta_impact_graph,
  19. parse_text,
  20. parse_entity_relation,
  21. handle_txt_graph,
  22. get_file_content,
  23. text_resource_solve,
  24. handle_id_unstructured,
  25. solve_unstructured_data
  26. )
  27. logger = logging.getLogger("app")
  28. # 配置MinIO客户端
  29. minio_client = Minio(
  30. Config.MINIO_HOST,
  31. access_key=Config.MINIO_USER,
  32. secret_key=Config.MINIO_PASSWORD,
  33. secure=True
  34. )
  35. # 配置文件上传相关
  36. UPLOAD_FOLDER = Config.UPLOAD_FOLDER
  37. bucket_name = Config.BUCKET_NAME
  38. prefix = Config.PREFIX
  39. # 允许的文件扩展名
  40. ALLOWED_EXTENSIONS = {'txt', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'csv'}
  41. def allowed_file(filename):
  42. """检查文件扩展名是否允许"""
  43. return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
  44. # 元数据列表
  45. @bp.route('/node/list', methods=['POST'])
  46. def meta_node_list():
  47. try:
  48. # 从请求中获取分页参数
  49. page = int(request.json.get('current', 1))
  50. page_size = int(request.json.get('size', 10))
  51. # 获取搜索参数
  52. search = request.json.get('search', '')
  53. en_name_filter = request.json.get('en_name', None)
  54. name_filter = request.json.get('name', None)
  55. category_filter = request.json.get('category', None)
  56. time_filter = request.json.get('time', None)
  57. tag_filter = request.json.get('tag', None)
  58. # 调用核心业务逻辑
  59. result, total_count = meta_list(
  60. page,
  61. page_size,
  62. search,
  63. en_name_filter,
  64. name_filter,
  65. category_filter,
  66. time_filter,
  67. tag_filter
  68. )
  69. # 返回结果
  70. return jsonify(success({
  71. "records": result,
  72. "total": total_count,
  73. "size": page_size,
  74. "current": page
  75. }))
  76. except Exception as e:
  77. logger.error(f"获取元数据列表失败: {str(e)}")
  78. return jsonify(failed(str(e)))
  79. # 元数据图谱
  80. @bp.route('/node/graph', methods=['POST'])
  81. def meta_node_graph():
  82. try:
  83. # 从请求中获取节点ID
  84. node_id = request.json.get('nodeId')
  85. # 调用核心业务逻辑
  86. result = meta_kinship_graph(node_id)
  87. # 返回结果
  88. return jsonify(success(result))
  89. except Exception as e:
  90. logger.error(f"获取元数据图谱失败: {str(e)}")
  91. return jsonify(failed(str(e)))
  92. # 删除元数据
  93. @bp.route('/node/delete', methods=['POST'])
  94. def meta_node_delete():
  95. try:
  96. # 从请求中获取节点ID
  97. node_id = request.json.get('id')
  98. # 删除节点逻辑
  99. with neo4j_driver.get_session() as session:
  100. cypher = "MATCH (n) WHERE id(n) = $node_id DETACH DELETE n"
  101. session.run(cypher, node_id=int(node_id))
  102. # 返回结果
  103. return jsonify(success({}))
  104. except Exception as e:
  105. logger.error(f"删除元数据失败: {str(e)}")
  106. return jsonify(failed(str(e)))
  107. # 编辑元数据
  108. @bp.route('/node/edit', methods=['POST'])
  109. def meta_node_edit():
  110. try:
  111. # 从请求中获取节点信息
  112. node_id = request.json.get('id')
  113. node_name = request.json.get('name')
  114. node_type = request.json.get('type')
  115. node_desc = request.json.get('desc', '')
  116. node_properties = request.json.get('properties', {})
  117. with neo4j_driver.get_session() as session:
  118. # 更新节点属性
  119. cypher = """
  120. MATCH (n) WHERE id(n) = $node_id
  121. SET n.name = $name, n.type = $type, n.desc = $desc,
  122. n.properties = $properties, n.updateTime = $update_time
  123. RETURN n
  124. """
  125. update_time = get_formatted_time()
  126. result = session.run(
  127. cypher,
  128. node_id=int(node_id),
  129. name=node_name,
  130. type=node_type,
  131. desc=node_desc,
  132. properties=node_properties,
  133. update_time=update_time
  134. )
  135. node = result.single()
  136. if node:
  137. return jsonify(success(dict(node["n"])))
  138. else:
  139. return jsonify(failed("节点不存在"))
  140. except Exception as e:
  141. logger.error(f"编辑元数据失败: {str(e)}")
  142. return jsonify(failed(str(e)))
  143. # 增加元数据
  144. @bp.route('/node/add', methods=['POST'])
  145. def meta_node_add():
  146. try:
  147. # 从请求中获取节点信息
  148. node_name = request.json.get('name')
  149. node_type = request.json.get('type')
  150. node_desc = request.json.get('desc', '')
  151. node_properties = request.json.get('properties', {})
  152. # 创建节点
  153. with neo4j_driver.get_session() as session:
  154. cypher = """
  155. CREATE (n:Metadata {name: $name, type: $type, desc: $desc,
  156. properties: $properties, createTime: $create_time,
  157. updateTime: $update_time})
  158. RETURN n
  159. """
  160. create_time = update_time = get_formatted_time()
  161. result = session.run(
  162. cypher,
  163. name=node_name,
  164. type=node_type,
  165. desc=node_desc,
  166. properties=node_properties,
  167. create_time=create_time,
  168. update_time=update_time
  169. )
  170. node = result.single()
  171. return jsonify(success(dict(node["n"])))
  172. except Exception as e:
  173. logger.error(f"添加元数据失败: {str(e)}")
  174. return jsonify(failed(str(e)))
  175. # 搜索元数据
  176. @bp.route('/search', methods=['GET'])
  177. def search_metadata_route():
  178. try:
  179. keyword = request.args.get('keyword', '')
  180. if not keyword:
  181. return jsonify(success([]))
  182. cypher = """
  183. MATCH (n:Metadata)
  184. WHERE n.name CONTAINS $keyword
  185. RETURN n LIMIT 100
  186. """
  187. with neo4j_driver.get_session() as session:
  188. result = session.run(cypher, keyword=keyword)
  189. metadata_list = [dict(record["n"]) for record in result]
  190. return jsonify(success(metadata_list))
  191. except Exception as e:
  192. logger.error(f"搜索元数据失败: {str(e)}")
  193. return jsonify(failed(str(e)))
  194. # 全文检索查询
  195. @bp.route('/full/text/query', methods=['POST'])
  196. def full_text_query():
  197. try:
  198. # 获取查询条件
  199. query = request.json.get('query', '')
  200. if not query:
  201. return jsonify(failed("查询条件不能为空"))
  202. # 执行Neo4j全文索引查询
  203. with neo4j_driver.get_session() as session:
  204. cypher = """
  205. CALL db.index.fulltext.queryNodes("metadataFulltext", $query)
  206. YIELD node, score
  207. RETURN node, score
  208. ORDER BY score DESC
  209. LIMIT 20
  210. """
  211. result = session.run(cypher, query=query)
  212. # 处理查询结果
  213. search_results = []
  214. for record in result:
  215. node_data = dict(record["node"])
  216. node_data["id"] = record["node"].id
  217. node_data["score"] = record["score"]
  218. search_results.append(node_data)
  219. return jsonify(success(search_results))
  220. except Exception as e:
  221. logger.error(f"全文检索查询失败: {str(e)}")
  222. return jsonify(failed(str(e)))
  223. # 非结构化文本查询
  224. @bp.route('/unstructure/text/query', methods=['POST'])
  225. def unstructure_text_query():
  226. try:
  227. # 获取查询参数
  228. node_id = request.json.get('id')
  229. if not node_id:
  230. return jsonify(failed("节点ID不能为空"))
  231. # 获取节点信息
  232. node_data = handle_id_unstructured(node_id)
  233. if not node_data:
  234. return jsonify(failed("节点不存在"))
  235. # 获取对象路径
  236. object_name = node_data.get('objectName')
  237. if not object_name:
  238. return jsonify(failed("文档路径不存在"))
  239. # 从MinIO获取文件内容
  240. file_content = get_file_content(minio_client, bucket_name, object_name)
  241. # 解析文本内容
  242. parsed_data = parse_text(file_content)
  243. # 返回结果
  244. result = {
  245. "node": node_data,
  246. "parsed": parsed_data,
  247. "content": file_content[:1000] + "..." if len(file_content) > 1000 else file_content
  248. }
  249. return jsonify(success(result))
  250. except Exception as e:
  251. logger.error(f"非结构化文本查询失败: {str(e)}")
  252. return jsonify(failed(str(e)))
  253. # 文件上传
  254. @bp.route('/resource/upload', methods=['POST'])
  255. def upload_file():
  256. try:
  257. # 检查请求中是否有文件
  258. if 'file' not in request.files:
  259. return jsonify(failed("没有找到上传的文件"))
  260. file = request.files['file']
  261. # 检查文件名
  262. if file.filename == '':
  263. return jsonify(failed("未选择文件"))
  264. # 检查文件类型
  265. if not allowed_file(file.filename):
  266. return jsonify(failed("不支持的文件类型"))
  267. # 上传到MinIO
  268. file_content = file.read()
  269. file_size = len(file_content)
  270. file_type = file.filename.rsplit('.', 1)[1].lower()
  271. # 生成唯一文件名
  272. object_name = f"{prefix}/{get_formatted_time()}_{file.filename}"
  273. # 上传文件
  274. minio_client.put_object(
  275. bucket_name,
  276. object_name,
  277. io.BytesIO(file_content),
  278. file_size,
  279. content_type=f"application/{file_type}"
  280. )
  281. # 返回结果
  282. return jsonify(success({
  283. "filename": file.filename,
  284. "size": file_size,
  285. "type": file_type,
  286. "objectName": object_name
  287. }))
  288. except Exception as e:
  289. logger.error(f"文件上传失败: {str(e)}")
  290. return jsonify(failed(str(e)))
  291. # 文件下载显示
  292. @bp.route('/resource/display', methods=['POST'])
  293. def upload_file_display():
  294. try:
  295. object_name = request.json.get('objectName')
  296. if not object_name:
  297. return jsonify(failed("文件路径不能为空"))
  298. # 获取文件内容
  299. try:
  300. response = minio_client.get_object(bucket_name, object_name)
  301. file_data = response.read()
  302. # 获取文件名
  303. file_name = object_name.split('/')[-1]
  304. # 确定文件类型
  305. file_extension = file_name.split('.')[-1].lower()
  306. # 为不同文件类型设置合适的MIME类型
  307. mime_types = {
  308. 'pdf': 'application/pdf',
  309. 'doc': 'application/msword',
  310. 'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
  311. 'xls': 'application/vnd.ms-excel',
  312. 'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  313. 'txt': 'text/plain',
  314. 'csv': 'text/csv'
  315. }
  316. content_type = mime_types.get(file_extension, 'application/octet-stream')
  317. # 返回结果
  318. return jsonify(success({
  319. "filename": file_name,
  320. "type": file_extension,
  321. "contentType": content_type,
  322. "size": len(file_data),
  323. "url": f"/api/meta/resource/download?objectName={object_name}"
  324. }))
  325. finally:
  326. response.close()
  327. response.release_conn()
  328. except Exception as e:
  329. logger.error(f"文件显示信息获取失败: {str(e)}")
  330. return jsonify(failed(str(e)))
  331. # 文件下载接口
  332. @bp.route('/resource/download', methods=['GET'])
  333. def download_file():
  334. try:
  335. object_name = request.args.get('objectName')
  336. if not object_name:
  337. return jsonify(failed("文件路径不能为空"))
  338. # 获取文件
  339. response = minio_client.get_object(bucket_name, object_name)
  340. file_data = response.read()
  341. # 获取文件名
  342. file_name = object_name.split('/')[-1]
  343. # 创建临时文件
  344. temp_path = os.path.join(UPLOAD_FOLDER, file_name)
  345. with open(temp_path, 'wb') as f:
  346. f.write(file_data)
  347. # 返回文件
  348. return send_file(
  349. temp_path,
  350. as_attachment=True,
  351. download_name=file_name
  352. )
  353. except Exception as e:
  354. logger.error(f"文件下载失败: {str(e)}")
  355. return jsonify(failed(str(e)))
  356. finally:
  357. if 'response' in locals():
  358. response.close()
  359. response.release_conn()
  360. # 文本资源翻译
  361. @bp.route('/resource/translate', methods=['POST'])
  362. def text_resource_translate():
  363. try:
  364. # 获取参数
  365. name = request.json.get('name', '')
  366. keyword = request.json.get('keyword', '')
  367. if not name:
  368. return jsonify(failed("名称不能为空"))
  369. # 调用资源处理逻辑
  370. result = text_resource_solve(None, name, keyword)
  371. return jsonify(success(result))
  372. except Exception as e:
  373. logger.error(f"文本资源翻译失败: {str(e)}")
  374. return jsonify(failed(str(e)))
  375. # 创建文本资源节点
  376. @bp.route('/resource/node', methods=['POST'])
  377. def text_resource_node():
  378. try:
  379. # 获取参数
  380. name = request.json.get('name', '')
  381. en_name = request.json.get('en_name', '')
  382. keywords = request.json.get('keywords', [])
  383. keywords_en = request.json.get('keywords_en', [])
  384. object_name = request.json.get('objectName', '')
  385. if not name or not en_name or not object_name:
  386. return jsonify(failed("参数不完整"))
  387. # 创建节点
  388. with neo4j_driver.get_session() as session:
  389. # 创建资源节点
  390. cypher = """
  391. CREATE (n:TextResource {
  392. name: $name,
  393. en_name: $en_name,
  394. keywords: $keywords,
  395. keywords_en: $keywords_en,
  396. objectName: $object_name,
  397. createTime: $create_time,
  398. updateTime: $update_time
  399. })
  400. RETURN n
  401. """
  402. create_time = update_time = get_formatted_time()
  403. result = session.run(
  404. cypher,
  405. name=name,
  406. en_name=en_name,
  407. keywords=keywords,
  408. keywords_en=keywords_en,
  409. object_name=object_name,
  410. create_time=create_time,
  411. update_time=update_time
  412. )
  413. node = result.single()["n"]
  414. # 为每个关键词创建标签节点并关联
  415. for i, keyword in enumerate(keywords):
  416. if keyword:
  417. # 创建标签节点
  418. tag_cypher = """
  419. MERGE (t:Tag {name: $name})
  420. ON CREATE SET t.en_name = $en_name, t.createTime = $create_time
  421. RETURN t
  422. """
  423. tag_result = session.run(
  424. tag_cypher,
  425. name=keyword,
  426. en_name=keywords_en[i] if i < len(keywords_en) else "",
  427. create_time=create_time
  428. )
  429. tag_node = tag_result.single()["t"]
  430. # 创建关系
  431. rel_cypher = """
  432. MATCH (n), (t)
  433. WHERE id(n) = $node_id AND id(t) = $tag_id
  434. CREATE (n)-[r:HAS_TAG]->(t)
  435. RETURN r
  436. """
  437. session.run(
  438. rel_cypher,
  439. node_id=node.id,
  440. tag_id=tag_node.id
  441. )
  442. # 返回创建的节点
  443. return jsonify(success(dict(node)))
  444. except Exception as e:
  445. logger.error(f"创建文本资源节点失败: {str(e)}")
  446. return jsonify(failed(str(e)))
  447. # 处理非结构化数据
  448. @bp.route('/unstructured/process', methods=['POST'])
  449. def processing_unstructured_data():
  450. try:
  451. # 获取参数
  452. node_id = request.json.get('id')
  453. if not node_id:
  454. return jsonify(failed("节点ID不能为空"))
  455. # 调用处理逻辑
  456. result = solve_unstructured_data(node_id, minio_client, prefix)
  457. if result:
  458. return jsonify(success({"message": "处理成功"}))
  459. else:
  460. return jsonify(failed("处理失败"))
  461. except Exception as e:
  462. logger.error(f"处理非结构化数据失败: {str(e)}")
  463. return jsonify(failed(str(e)))
  464. # 创建文本图谱
  465. @bp.route('/text/graph', methods=['POST'])
  466. def create_text_graph():
  467. try:
  468. # 获取参数
  469. node_id = request.json.get('id')
  470. entity = request.json.get('entity')
  471. entity_en = request.json.get('entity_en')
  472. if not all([node_id, entity, entity_en]):
  473. return jsonify(failed("参数不完整"))
  474. # 创建图谱
  475. result = handle_txt_graph(node_id, entity, entity_en)
  476. if result:
  477. return jsonify(success({"message": "图谱创建成功"}))
  478. else:
  479. return jsonify(failed("图谱创建失败"))
  480. except Exception as e:
  481. logger.error(f"创建文本图谱失败: {str(e)}")
  482. return jsonify(failed(str(e)))