routes.py 41 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331
  1. from flask import jsonify, request, make_response, Blueprint, current_app, send_file
  2. from app.api.data_parse import bp
  3. from app.core.data_parse.parse import parse_data, process_business_card, update_business_card, get_business_cards, update_business_card_status, create_talent_tag, get_talent_tag_list, update_talent_tag, delete_talent_tag, query_neo4j_graph, talent_get_tags, talent_update_tags, get_business_card, get_hotel_positions_list, add_hotel_positions, update_hotel_positions, query_hotel_positions, delete_hotel_positions, get_hotel_group_brands_list, add_hotel_group_brands, update_hotel_group_brands, query_hotel_group_brands, delete_hotel_group_brands, get_duplicate_records, process_duplicate_record, get_duplicate_record_detail
  4. from app.config.config import DevelopmentConfig, ProductionConfig
  5. import logging
  6. import boto3
  7. from botocore.config import Config
  8. from botocore.exceptions import ClientError
  9. from io import BytesIO
  10. import base64
  11. import os
  12. import urllib.parse
  13. from minio import Minio
  14. """
  15. DataOps平台 - 数据解析API路由模块
  16. 本模块包含以下功能的API接口:
  17. 1. 名片解析功能
  18. - POST /business-card-parse: 上传名片图片并解析信息
  19. - PUT /business-cards/<id>: 更新名片信息
  20. - GET /get-business-cards: 获取所有名片记录
  21. - GET /get-business-card/<id>: 获取指定ID的名片记录
  22. - PUT /update-business-cards/<id>/status: 更新名片状态
  23. 2. 重复记录处理功能(新增)
  24. - GET /get-duplicate-records[?status=<status>]: 获取重复记录列表
  25. - POST /process-duplicate-record/<id>: 处理重复记录
  26. - GET /get-duplicate-record-detail/<id>: 获取重复记录详情
  27. 3. 人才标签管理功能
  28. - POST /create-talent-tag: 创建人才标签
  29. - GET /get-talent-tag-list: 获取人才标签列表
  30. - PUT /update-talent-tag/<id>: 更新人才标签
  31. - DELETE /delete-talent-tag/<id>: 删除人才标签
  32. 4. 人才标签关系管理功能
  33. - GET /talent-get-tags/<talent_id>: 获取人才关联的标签
  34. - POST /talent-update-tags: 批量更新人才标签关系
  35. 5. 知识图谱查询功能
  36. - POST /query-kg: 通过自然语言查询图数据库
  37. 6. 酒店职位数据管理功能
  38. - GET /get-hotel-positions-list: 获取酒店职位列表
  39. - POST /add-hotel-positions: 新增酒店职位记录
  40. - PUT /update-hotel-positions/<id>: 更新酒店职位记录
  41. - GET /query-hotel-positions/<id>: 查询指定职位记录
  42. - DELETE /delete-hotel-positions/<id>: 删除职位记录
  43. 7. 酒店集团品牌数据管理功能
  44. - GET /get-hotel-group-brands-list: 获取酒店集团品牌列表
  45. - POST /add-hotel-group-brands: 新增酒店集团品牌记录
  46. - PUT /update-hotel-group-brands/<id>: 更新酒店集团品牌记录
  47. - GET /query-hotel-group-brands/<id>: 查询指定品牌记录
  48. - DELETE /delete-hotel-group-brands/<id>: 删除品牌记录
  49. 8. MinIO文件管理功能
  50. - GET /business-cards/image/<path>: 获取名片图片
  51. - GET /test-minio-connection: 测试MinIO连接
  52. 重复记录处理API详细说明:
  53. ═══════════════════════════════
  54. 1. 获取重复记录列表
  55. GET /get-duplicate-records[?status=<status>]
  56. - 查询参数: status (可选): 'pending'/'processed'/'ignored'
  57. - 返回: 重复记录列表,包含主记录和疑似重复记录信息
  58. 2. 处理重复记录
  59. POST /process-duplicate-record/<duplicate_id>
  60. - 路径参数: duplicate_id (必填): 重复记录ID
  61. - 请求体参数:
  62. * action (必填): 'merge_to_suspected'/'keep_main'/'ignore'
  63. * selected_duplicate_id (可选): 当action为merge_to_suspected时必填
  64. * processed_by (可选): 处理人标识
  65. * notes (可选): 处理备注
  66. - 处理动作说明:
  67. * merge_to_suspected: 合并到选中的疑似重复记录,删除主记录
  68. * keep_main: 保留主记录,标记为已处理
  69. * ignore: 忽略重复提醒,标记为已处理
  70. 3. 获取重复记录详情
  71. GET /get-duplicate-record-detail/<duplicate_id>
  72. - 路径参数: duplicate_id (必填): 重复记录ID
  73. - 返回: 重复记录的详细信息,包含主记录和所有疑似重复记录
  74. 业务流程说明:
  75. 1. 上传名片后,系统自动检测重复记录
  76. 2. 如发现重复,创建主记录并生成重复记录条目
  77. 3. 管理员通过API查看待处理的重复记录
  78. 4. 管理员选择处理方式:合并、保留或忽略
  79. 5. 系统根据选择执行相应操作并更新状态
  80. """
  81. # Define logger
  82. logger = logging.getLogger(__name__)
  83. # For failure responses
  84. def failed(message, code=500):
  85. return {
  86. 'success': False,
  87. 'message': message,
  88. 'data': None
  89. }, code
  90. # 根据环境选择配置
  91. if os.environ.get('FLASK_ENV') == 'production':
  92. config = ProductionConfig()
  93. else:
  94. config = DevelopmentConfig()
  95. # 使用配置变量
  96. minio_url = f"{'https' if config.MINIO_SECURE else 'http'}://{config.MINIO_HOST}"
  97. minio_access_key = config.MINIO_USER
  98. minio_secret_key = config.MINIO_PASSWORD
  99. minio_bucket = config.MINIO_BUCKET
  100. use_ssl = config.MINIO_SECURE
  101. def get_minio_client():
  102. """获取 MinIO 客户端实例"""
  103. return Minio(
  104. '192.168.3.143:9000',
  105. access_key=config.MINIO_USER,
  106. secret_key=config.MINIO_PASSWORD,
  107. secure=config.MINIO_SECURE
  108. )
  109. # 测试用的解析数据接口。没有实际使用。
  110. @bp.route('/parse', methods=['POST'])
  111. def parse():
  112. """
  113. 解析数据的接口
  114. """
  115. try:
  116. data = request.get_json()
  117. if not data:
  118. return jsonify({'error': 'No data provided'}), 400
  119. result = parse_data(data)
  120. return jsonify(result), 200
  121. except Exception as e:
  122. return jsonify({'error': str(e)}), 500
  123. # 名片解析接口
  124. @bp.route('/business-card-parse', methods=['POST'])
  125. def parse_business_card_route():
  126. """
  127. 处理名片图片并提取信息的API接口
  128. 请求参数:
  129. - image: 名片图片文件 (multipart/form-data)
  130. 返回:
  131. - JSON: 包含提取的名片信息和处理状态
  132. """
  133. # 检查是否上传了文件
  134. if 'image' not in request.files:
  135. return jsonify({
  136. 'success': False,
  137. 'message': '未上传图片',
  138. 'data': None
  139. }), 400
  140. image_file = request.files['image']
  141. # 检查文件是否为空
  142. if image_file.filename == '':
  143. return jsonify({
  144. 'success': False,
  145. 'message': '未选择文件',
  146. 'data': None
  147. }), 400
  148. # 检查文件类型是否为图片
  149. if not image_file.content_type.startswith('image/'):
  150. return jsonify({
  151. 'success': False,
  152. 'message': '上传的文件不是图片',
  153. 'data': None
  154. }), 400
  155. # 处理名片图片
  156. result = process_business_card(image_file)
  157. if result['success']:
  158. return jsonify(result), 200
  159. else:
  160. return jsonify(result), 500
  161. # 更新名片信息接口
  162. @bp.route('/business-cards/<int:card_id>', methods=['PUT'])
  163. def update_business_card_route(card_id):
  164. """
  165. 更新名片信息的API接口
  166. 路径参数:
  167. - card_id: 名片记录ID
  168. 请求参数:
  169. - JSON格式的名片信息
  170. 返回:
  171. - JSON: 包含更新后的名片信息和处理状态
  172. """
  173. # 获取请求数据
  174. data = request.json
  175. if not data:
  176. return jsonify({
  177. 'success': False,
  178. 'message': '请求数据为空',
  179. 'data': None
  180. }), 400
  181. # 调用业务逻辑函数处理更新
  182. result = update_business_card(card_id, data)
  183. # 根据处理结果设置HTTP状态码
  184. status_code = 200 if result['success'] else 500
  185. if 'not found' in result.get('message', '').lower() or '未找到' in result.get('message', ''):
  186. status_code = 404
  187. return jsonify(result), status_code
  188. # 获取所有名片记录的API接口
  189. @bp.route('/get-business-cards', methods=['GET'])
  190. def get_business_cards_route():
  191. """
  192. 获取所有名片记录的API接口
  193. 返回:
  194. - JSON: 包含名片记录列表和处理状态
  195. """
  196. # 调用业务逻辑函数获取名片列表
  197. result = get_business_cards()
  198. # 根据处理结果设置HTTP状态码
  199. status_code = 200 if result['success'] else 500
  200. return jsonify(result), status_code
  201. @bp.route('/update-business-cards/<int:card_id>/status', methods=['PUT'])
  202. def update_business_card_status_route(card_id):
  203. """
  204. 更新名片状态的API接口
  205. 路径参数:
  206. - card_id: 名片记录ID
  207. 请求参数:
  208. - JSON格式,包含status字段
  209. 返回:
  210. - JSON: 包含更新后的名片信息和处理状态
  211. """
  212. # 获取请求数据
  213. data = request.json
  214. if not data or 'status' not in data:
  215. return jsonify({
  216. 'success': False,
  217. 'message': '请求数据为空或缺少status字段',
  218. 'data': None
  219. }), 400
  220. status = data['status']
  221. # 调用业务逻辑函数处理状态更新
  222. result = update_business_card_status(card_id, status)
  223. # 根据处理结果设置HTTP状态码
  224. status_code = 200 if result['success'] else 500
  225. if 'not found' in result.get('message', '').lower() or '未找到' in result.get('message', ''):
  226. status_code = 404
  227. return jsonify(result), status_code
  228. # 从MinIO获取名片图片的API接口
  229. @bp.route('/business-cards/image/<path:image_path>', methods=['GET'])
  230. def get_business_card_image(image_path):
  231. """
  232. 从MinIO获取名片图片的API接口
  233. 路径参数:
  234. - image_path: MinIO中的图片路径
  235. 返回:
  236. - 图片数据流
  237. """
  238. try:
  239. # 记录下载请求信息,便于调试
  240. logger.info(f"获取名片图片请求: {image_path}")
  241. # 获取 MinIO 客户端
  242. minio_client = get_minio_client()
  243. if not minio_client:
  244. return jsonify(failed("MinIO客户端初始化失败")), 500
  245. try:
  246. # 使用正确的MinIO客户端方法
  247. data = minio_client.get_object(minio_bucket, image_path)
  248. # 创建内存文件流
  249. file_stream = BytesIO(data.read())
  250. # 获取文件名
  251. file_name = image_path.split('/')[-1]
  252. # 返回文件
  253. return send_file(
  254. file_stream,
  255. as_attachment=False, # 设置为False,让浏览器直接显示图片
  256. download_name=file_name,
  257. mimetype='image/jpeg' # 根据实际图片类型设置
  258. )
  259. except Exception as e:
  260. logger.error(f"MinIO获取文件失败: {str(e)}")
  261. return jsonify(failed(f"文件获取失败: {str(e)}")), 404
  262. except Exception as e:
  263. logger.error(f"文件下载失败: {str(e)}")
  264. return jsonify(failed(str(e))), 500
  265. finally:
  266. # 确保关闭数据流
  267. if 'data' in locals():
  268. data.close()
  269. # 创建人才标签接口
  270. @bp.route('/create-talent-tag', methods=['POST'])
  271. def create_talent_tag_route():
  272. """
  273. 创建人才标签的API接口
  274. 请求参数:
  275. - JSON格式,包含以下字段:
  276. - name: 标签名称
  277. - category: 标签分类
  278. - description: 标签描述
  279. - status: 启用状态,默认为'active'
  280. 返回:
  281. - JSON: 包含创建结果和标签信息
  282. """
  283. try:
  284. # 获取请求数据
  285. data = request.get_json()
  286. if not data:
  287. return jsonify({
  288. 'success': False,
  289. 'message': '请求数据为空',
  290. 'data': None
  291. }), 400
  292. # 验证必要字段
  293. if 'name' not in data or not data['name']:
  294. return jsonify({
  295. 'success': False,
  296. 'message': '标签名称不能为空',
  297. 'data': None
  298. }), 400
  299. # 处理分类字段,如果未提供则设置默认值
  300. if 'category' not in data or not data['category']:
  301. data['category'] = '未分类'
  302. # 调用业务逻辑函数处理创建
  303. result = create_talent_tag(data)
  304. # 根据处理结果设置HTTP状态码
  305. status_code = 200 if result['success'] else 500
  306. return jsonify(result), status_code
  307. except Exception as e:
  308. logger.error(f"创建人才标签失败: {str(e)}")
  309. return jsonify({
  310. 'success': False,
  311. 'message': f'创建人才标签失败: {str(e)}',
  312. 'data': None
  313. }), 500
  314. # 获取人才标签列表接口
  315. @bp.route('/get-talent-tag-list', methods=['GET'])
  316. def get_talent_tag_list_route():
  317. """
  318. 获取人才标签列表的API接口
  319. 返回:
  320. - JSON: 包含人才标签列表和处理状态
  321. """
  322. try:
  323. # 调用业务逻辑函数获取人才标签列表
  324. result = get_talent_tag_list()
  325. # 根据处理结果设置HTTP状态码
  326. status_code = 200 if result['success'] else 500
  327. return jsonify(result), status_code
  328. except Exception as e:
  329. logger.error(f"获取人才标签列表失败: {str(e)}")
  330. return jsonify({
  331. 'success': False,
  332. 'message': f'获取人才标签列表失败: {str(e)}',
  333. 'data': []
  334. }), 500
  335. # 更新人才标签接口
  336. @bp.route('/update-talent-tag/<int:tag_id>', methods=['PUT'])
  337. def update_talent_tag_route(tag_id):
  338. """
  339. 更新人才标签的API接口
  340. 路径参数:
  341. - tag_id: 标签节点ID
  342. 请求参数:
  343. - JSON格式,可能包含以下字段:
  344. - name: 标签名称
  345. - category: 标签分类
  346. - description: 标签描述
  347. - status: 启用状态
  348. 返回:
  349. - JSON: 包含更新结果和标签信息
  350. """
  351. try:
  352. # 获取请求数据
  353. data = request.get_json()
  354. if not data:
  355. return jsonify({
  356. 'success': False,
  357. 'message': '请求数据为空',
  358. 'data': None
  359. }), 400
  360. # 调用业务逻辑函数处理更新
  361. result = update_talent_tag(tag_id, data)
  362. # 根据处理结果设置HTTP状态码
  363. if not result['success']:
  364. if result['code'] == 404:
  365. status_code = 404
  366. elif result['code'] == 400:
  367. status_code = 400
  368. else:
  369. status_code = 500
  370. else:
  371. status_code = 200
  372. return jsonify(result), status_code
  373. except Exception as e:
  374. logger.error(f"更新人才标签失败: {str(e)}")
  375. return jsonify({
  376. 'success': False,
  377. 'message': f'更新人才标签失败: {str(e)}',
  378. 'data': None
  379. }), 500
  380. # 删除人才标签接口
  381. @bp.route('/delete-talent-tag/<int:tag_id>', methods=['DELETE'])
  382. def delete_talent_tag_route(tag_id):
  383. """
  384. 删除人才标签的API接口
  385. 路径参数:
  386. - tag_id: 标签节点ID
  387. 返回:
  388. - JSON: 包含删除结果和被删除的标签信息
  389. """
  390. try:
  391. # 调用业务逻辑函数执行删除
  392. result = delete_talent_tag(tag_id)
  393. # 根据处理结果设置HTTP状态码
  394. if not result['success']:
  395. if result['code'] == 404:
  396. status_code = 404
  397. else:
  398. status_code = 500
  399. else:
  400. status_code = 200
  401. return jsonify(result), status_code
  402. except Exception as e:
  403. logger.error(f"删除人才标签失败: {str(e)}")
  404. return jsonify({
  405. 'success': False,
  406. 'message': f'删除人才标签失败: {str(e)}',
  407. 'data': None
  408. }), 500
  409. @bp.route('/query-kg', methods=['POST'])
  410. def query_kg():
  411. """
  412. 查询知识图谱API接口
  413. 请求参数:
  414. - query_requirement: 查询需求描述(JSON格式)
  415. 返回:
  416. - JSON: 包含查询结果和处理状态
  417. """
  418. try:
  419. # 获取请求数据
  420. data = request.json
  421. if not data or 'query_requirement' not in data:
  422. return jsonify({
  423. 'code': 400,
  424. 'success': False,
  425. 'message': '请求数据为空或缺少query_requirement字段',
  426. 'data': []
  427. }), 400
  428. query_requirement = data['query_requirement']
  429. # 调用业务逻辑函数执行查询
  430. result = query_neo4j_graph(query_requirement)
  431. # 根据处理结果设置HTTP状态码
  432. status_code = 200 if result['success'] else 500
  433. return jsonify(result), status_code
  434. except Exception as e:
  435. logger.error(f"查询知识图谱失败: {str(e)}")
  436. return jsonify({
  437. 'code': 500,
  438. 'success': False,
  439. 'message': f"查询知识图谱失败: {str(e)}",
  440. 'data': []
  441. }), 500
  442. @bp.route('/talent-get-tags/<int:talent_id>', methods=['GET'])
  443. def talent_get_tags_route(talent_id):
  444. """
  445. 获取人才标签的API接口
  446. 路径参数:
  447. - talent_id: 人才节点ID
  448. 返回:
  449. - JSON: 包含人才关联的标签列表和处理状态
  450. """
  451. try:
  452. # 调用业务逻辑函数获取人才标签
  453. result = talent_get_tags(talent_id)
  454. # 根据处理结果设置HTTP状态码
  455. status_code = 200 if result['success'] else 500
  456. return jsonify(result), status_code
  457. except Exception as e:
  458. logger.error(f"获取人才标签失败: {str(e)}")
  459. return jsonify({
  460. 'code': 500,
  461. 'success': False,
  462. 'message': f"获取人才标签失败: {str(e)}",
  463. 'data': []
  464. }), 500
  465. @bp.route('/talent-update-tags', methods=['POST'])
  466. def talent_update_tags_route():
  467. """
  468. 更新人才标签关系的API接口
  469. 请求参数:
  470. - JSON数组,包含talent和tag字段的对象列表
  471. 例如: [
  472. {"talent": 12345, "tag": "市场营销"},
  473. {"talent": 12345, "tag": "酒店管理"}
  474. ]
  475. 返回:
  476. - JSON: 包含更新结果的状态信息
  477. """
  478. try:
  479. # 获取请求数据
  480. data = request.json
  481. if not data:
  482. return jsonify({
  483. 'code': 400,
  484. 'success': False,
  485. 'message': '请求数据为空',
  486. 'data': None
  487. }), 400
  488. # 调用业务逻辑函数处理标签关系更新
  489. result = talent_update_tags(data)
  490. # 根据处理结果设置HTTP状态码
  491. if result['code'] == 200:
  492. status_code = 200
  493. elif result['code'] == 206:
  494. status_code = 206 # Partial Content
  495. elif result['code'] == 400:
  496. status_code = 400 # Bad Request
  497. elif result['code'] == 404:
  498. status_code = 404 # Not Found
  499. else:
  500. status_code = 500 # Internal Server Error
  501. return jsonify(result), status_code
  502. except Exception as e:
  503. logger.error(f"更新人才标签关系失败: {str(e)}")
  504. return jsonify({
  505. 'code': 500,
  506. 'success': False,
  507. 'message': f"更新人才标签关系失败: {str(e)}",
  508. 'data': None
  509. }), 500
  510. # 测试MinIO连接
  511. def test_minio_connection():
  512. """测试MinIO连接是否正常"""
  513. try:
  514. client = get_minio_client()
  515. if client.bucket_exists(minio_bucket):
  516. return {
  517. 'success': True,
  518. 'message': f'连接MinIO服务器成功,存储桶 {minio_bucket} 存在',
  519. 'config': {
  520. 'host': config.MINIO_HOST,
  521. 'bucket': minio_bucket,
  522. 'secure': use_ssl
  523. }
  524. }
  525. else:
  526. return {
  527. 'success': False,
  528. 'message': f'连接MinIO服务器成功,但存储桶 {minio_bucket} 不存在',
  529. 'config': {
  530. 'host': config.MINIO_HOST,
  531. 'bucket': minio_bucket,
  532. 'secure': use_ssl
  533. }
  534. }
  535. except Exception as e:
  536. return {
  537. 'success': False,
  538. 'message': f'连接MinIO服务器失败: {str(e)}',
  539. 'config': {
  540. 'host': config.MINIO_HOST,
  541. 'bucket': minio_bucket,
  542. 'secure': use_ssl
  543. }
  544. }
  545. # MinIO测试接口
  546. @bp.route('/test-minio-connection', methods=['GET'])
  547. def test_minio_connection_route():
  548. """
  549. 测试MinIO连接的API接口
  550. 返回:
  551. - JSON: 包含连接测试结果
  552. """
  553. try:
  554. result = test_minio_connection()
  555. status_code = 200 if result['success'] else 500
  556. return jsonify(result), status_code
  557. except Exception as e:
  558. logger.error(f"测试MinIO连接失败: {str(e)}")
  559. return jsonify({
  560. 'success': False,
  561. 'message': f'测试MinIO连接失败: {str(e)}',
  562. 'config': {
  563. 'host': config.MINIO_HOST,
  564. 'bucket': minio_bucket,
  565. 'secure': use_ssl
  566. }
  567. }), 500
  568. # 获取单个名片记录的API接口
  569. @bp.route('/get-business-card/<int:card_id>', methods=['GET'])
  570. def get_business_card_route(card_id):
  571. """
  572. 获取单个名片记录的API接口
  573. 路径参数:
  574. - card_id: 名片记录ID
  575. 返回:
  576. - JSON: 包含名片记录信息和处理状态
  577. """
  578. # 调用业务逻辑函数获取名片记录
  579. result = get_business_card(card_id)
  580. # 根据处理结果设置HTTP状态码
  581. if not result['success']:
  582. if result['code'] == 404:
  583. status_code = 404
  584. else:
  585. status_code = 500
  586. else:
  587. status_code = 200
  588. return jsonify(result), status_code
  589. @bp.route('/get-hotel-positions-list', methods=['GET'])
  590. def get_hotel_positions_list_route():
  591. """
  592. 获取酒店职位数据表全部记录的API接口
  593. 返回:
  594. - JSON: 包含酒店职位记录列表和处理状态
  595. """
  596. try:
  597. # 调用业务逻辑函数获取酒店职位列表
  598. result = get_hotel_positions_list()
  599. # 根据处理结果设置HTTP状态码
  600. status_code = 200 if result['success'] else 500
  601. return jsonify(result), status_code
  602. except Exception as e:
  603. # 处理未预期的异常
  604. error_msg = f"获取酒店职位列表时发生错误: {str(e)}"
  605. logger.error(error_msg, exc_info=True)
  606. return jsonify({
  607. 'success': False,
  608. 'message': error_msg,
  609. 'data': [],
  610. 'count': 0
  611. }), 500
  612. @bp.route('/add-hotel-positions', methods=['POST'])
  613. def add_hotel_positions_route():
  614. """
  615. 新增酒店职位数据表记录的API接口
  616. 请求参数:
  617. - JSON格式,包含以下字段:
  618. - department_zh: 部门中文名称 (必填)
  619. - department_en: 部门英文名称 (必填)
  620. - position_zh: 职位中文名称 (必填)
  621. - position_en: 职位英文名称 (必填)
  622. - position_abbr: 职位英文缩写 (可选)
  623. - level_zh: 职级中文名称 (必填)
  624. - level_en: 职级英文名称 (必填)
  625. - created_by: 创建者 (可选)
  626. - updated_by: 更新者 (可选)
  627. - status: 状态 (可选)
  628. 返回:
  629. - JSON: 包含创建结果和职位信息
  630. """
  631. try:
  632. # 获取请求数据
  633. data = request.get_json()
  634. if not data:
  635. return jsonify({
  636. 'success': False,
  637. 'message': '请求数据为空',
  638. 'data': None
  639. }), 400
  640. # 调用业务逻辑函数处理创建
  641. result = add_hotel_positions(data)
  642. # 根据处理结果设置HTTP状态码
  643. if result['code'] == 200:
  644. status_code = 201 # Created
  645. elif result['code'] == 400:
  646. status_code = 400 # Bad Request
  647. elif result['code'] == 409:
  648. status_code = 409 # Conflict
  649. else:
  650. status_code = 500 # Internal Server Error
  651. return jsonify(result), status_code
  652. except Exception as e:
  653. # 处理未预期的异常
  654. error_msg = f"创建酒店职位记录时发生错误: {str(e)}"
  655. logger.error(error_msg, exc_info=True)
  656. return jsonify({
  657. 'success': False,
  658. 'message': error_msg,
  659. 'data': None
  660. }), 500
  661. @bp.route('/update-hotel-positions/<int:position_id>', methods=['PUT'])
  662. def update_hotel_positions_route(position_id):
  663. """
  664. 修改酒店职位数据表记录的API接口
  665. 路径参数:
  666. - position_id: 职位记录ID
  667. 请求参数:
  668. - JSON格式,可能包含以下字段:
  669. - department_zh: 部门中文名称
  670. - department_en: 部门英文名称
  671. - position_zh: 职位中文名称
  672. - position_en: 职位英文名称
  673. - position_abbr: 职位英文缩写
  674. - level_zh: 职级中文名称
  675. - level_en: 职级英文名称
  676. - updated_by: 更新者
  677. - status: 状态
  678. 返回:
  679. - JSON: 包含更新结果和职位信息
  680. """
  681. try:
  682. # 获取请求数据
  683. data = request.get_json()
  684. if not data:
  685. return jsonify({
  686. 'success': False,
  687. 'message': '请求数据为空',
  688. 'data': None
  689. }), 400
  690. # 调用业务逻辑函数处理更新
  691. result = update_hotel_positions(position_id, data)
  692. # 根据处理结果设置HTTP状态码
  693. if result['code'] == 200:
  694. status_code = 200 # OK
  695. elif result['code'] == 400:
  696. status_code = 400 # Bad Request
  697. elif result['code'] == 404:
  698. status_code = 404 # Not Found
  699. elif result['code'] == 409:
  700. status_code = 409 # Conflict
  701. else:
  702. status_code = 500 # Internal Server Error
  703. return jsonify(result), status_code
  704. except Exception as e:
  705. # 处理未预期的异常
  706. error_msg = f"更新酒店职位记录时发生错误: {str(e)}"
  707. logger.error(error_msg, exc_info=True)
  708. return jsonify({
  709. 'success': False,
  710. 'message': error_msg,
  711. 'data': None
  712. }), 500
  713. @bp.route('/query-hotel-positions/<int:position_id>', methods=['GET'])
  714. def query_hotel_positions_route(position_id):
  715. """
  716. 查找指定ID的酒店职位数据表记录的API接口
  717. 路径参数:
  718. - position_id: 职位记录ID
  719. 返回:
  720. - JSON: 包含查找结果和职位信息
  721. """
  722. try:
  723. # 调用业务逻辑函数查找职位记录
  724. result = query_hotel_positions(position_id)
  725. # 根据处理结果设置HTTP状态码
  726. if result['code'] == 200:
  727. status_code = 200 # OK
  728. elif result['code'] == 404:
  729. status_code = 404 # Not Found
  730. else:
  731. status_code = 500 # Internal Server Error
  732. return jsonify(result), status_code
  733. except Exception as e:
  734. # 处理未预期的异常
  735. error_msg = f"查找酒店职位记录时发生错误: {str(e)}"
  736. logger.error(error_msg, exc_info=True)
  737. return jsonify({
  738. 'success': False,
  739. 'message': error_msg,
  740. 'data': None
  741. }), 500
  742. @bp.route('/delete-hotel-positions/<int:position_id>', methods=['DELETE'])
  743. def delete_hotel_positions_route(position_id):
  744. """
  745. 删除指定ID的酒店职位数据表记录的API接口
  746. 路径参数:
  747. - position_id: 职位记录ID
  748. 返回:
  749. - JSON: 包含删除结果和被删除的职位信息
  750. """
  751. try:
  752. # 调用业务逻辑函数删除职位记录
  753. result = delete_hotel_positions(position_id)
  754. # 根据处理结果设置HTTP状态码
  755. if result['code'] == 200:
  756. status_code = 200 # OK
  757. elif result['code'] == 404:
  758. status_code = 404 # Not Found
  759. else:
  760. status_code = 500 # Internal Server Error
  761. return jsonify(result), status_code
  762. except Exception as e:
  763. # 处理未预期的异常
  764. error_msg = f"删除酒店职位记录时发生错误: {str(e)}"
  765. logger.error(error_msg, exc_info=True)
  766. return jsonify({
  767. 'success': False,
  768. 'message': error_msg,
  769. 'data': None
  770. }), 500
  771. @bp.route('/get-hotel-group-brands-list', methods=['GET'])
  772. def get_hotel_group_brands_list_route():
  773. """
  774. 获取酒店集团子品牌数据表全部记录的API接口
  775. 返回:
  776. - JSON: 包含酒店集团品牌记录列表和处理状态
  777. """
  778. try:
  779. # 调用业务逻辑函数获取酒店集团品牌列表
  780. result = get_hotel_group_brands_list()
  781. # 根据处理结果设置HTTP状态码
  782. status_code = 200 if result['success'] else 500
  783. return jsonify(result), status_code
  784. except Exception as e:
  785. # 处理未预期的异常
  786. error_msg = f"获取酒店集团品牌列表时发生错误: {str(e)}"
  787. logger.error(error_msg, exc_info=True)
  788. return jsonify({
  789. 'success': False,
  790. 'message': error_msg,
  791. 'data': [],
  792. 'count': 0
  793. }), 500
  794. @bp.route('/add-hotel-group-brands', methods=['POST'])
  795. def add_hotel_group_brands_route():
  796. """
  797. 新增酒店集团子品牌数据表记录的API接口
  798. 请求参数:
  799. - JSON格式,包含以下字段:
  800. - group_name_en: 集团英文名称 (必填)
  801. - group_name_zh: 集团中文名称 (必填)
  802. - brand_name_en: 品牌英文名称 (必填)
  803. - brand_name_zh: 品牌中文名称 (必填)
  804. - positioning_level_en: 定位级别英文名称 (必填)
  805. - positioning_level_zh: 定位级别中文名称 (必填)
  806. - created_by: 创建者 (可选)
  807. - updated_by: 更新者 (可选)
  808. - status: 状态 (可选)
  809. 返回:
  810. - JSON: 包含创建结果和品牌信息
  811. """
  812. try:
  813. # 获取请求数据
  814. data = request.get_json()
  815. if not data:
  816. return jsonify({
  817. 'success': False,
  818. 'message': '请求数据为空',
  819. 'data': None
  820. }), 400
  821. # 调用业务逻辑函数处理创建
  822. result = add_hotel_group_brands(data)
  823. # 根据处理结果设置HTTP状态码
  824. if result['code'] == 200:
  825. status_code = 201 # Created
  826. elif result['code'] == 400:
  827. status_code = 400 # Bad Request
  828. elif result['code'] == 409:
  829. status_code = 409 # Conflict
  830. else:
  831. status_code = 500 # Internal Server Error
  832. return jsonify(result), status_code
  833. except Exception as e:
  834. # 处理未预期的异常
  835. error_msg = f"创建酒店集团品牌记录时发生错误: {str(e)}"
  836. logger.error(error_msg, exc_info=True)
  837. return jsonify({
  838. 'success': False,
  839. 'message': error_msg,
  840. 'data': None
  841. }), 500
  842. @bp.route('/update-hotel-group-brands/<int:brand_id>', methods=['PUT'])
  843. def update_hotel_group_brands_route(brand_id):
  844. """
  845. 修改酒店集团子品牌数据表记录的API接口
  846. 路径参数:
  847. - brand_id: 品牌记录ID
  848. 请求参数:
  849. - JSON格式,可能包含以下字段:
  850. - group_name_en: 集团英文名称
  851. - group_name_zh: 集团中文名称
  852. - brand_name_en: 品牌英文名称
  853. - brand_name_zh: 品牌中文名称
  854. - positioning_level_en: 定位级别英文名称
  855. - positioning_level_zh: 定位级别中文名称
  856. - updated_by: 更新者
  857. - status: 状态
  858. 返回:
  859. - JSON: 包含更新结果和品牌信息
  860. """
  861. try:
  862. # 获取请求数据
  863. data = request.get_json()
  864. if not data:
  865. return jsonify({
  866. 'success': False,
  867. 'message': '请求数据为空',
  868. 'data': None
  869. }), 400
  870. # 调用业务逻辑函数处理更新
  871. result = update_hotel_group_brands(brand_id, data)
  872. # 根据处理结果设置HTTP状态码
  873. if result['code'] == 200:
  874. status_code = 200 # OK
  875. elif result['code'] == 400:
  876. status_code = 400 # Bad Request
  877. elif result['code'] == 404:
  878. status_code = 404 # Not Found
  879. elif result['code'] == 409:
  880. status_code = 409 # Conflict
  881. else:
  882. status_code = 500 # Internal Server Error
  883. return jsonify(result), status_code
  884. except Exception as e:
  885. # 处理未预期的异常
  886. error_msg = f"更新酒店集团品牌记录时发生错误: {str(e)}"
  887. logger.error(error_msg, exc_info=True)
  888. return jsonify({
  889. 'success': False,
  890. 'message': error_msg,
  891. 'data': None
  892. }), 500
  893. @bp.route('/query-hotel-group-brands/<int:brand_id>', methods=['GET'])
  894. def query_hotel_group_brands_route(brand_id):
  895. """
  896. 查找指定ID的酒店集团子品牌数据表记录的API接口
  897. 路径参数:
  898. - brand_id: 品牌记录ID
  899. 返回:
  900. - JSON: 包含查找结果和品牌信息
  901. """
  902. try:
  903. # 调用业务逻辑函数查找品牌记录
  904. result = query_hotel_group_brands(brand_id)
  905. # 根据处理结果设置HTTP状态码
  906. if result['code'] == 200:
  907. status_code = 200 # OK
  908. elif result['code'] == 404:
  909. status_code = 404 # Not Found
  910. else:
  911. status_code = 500 # Internal Server Error
  912. return jsonify(result), status_code
  913. except Exception as e:
  914. # 处理未预期的异常
  915. error_msg = f"查找酒店集团品牌记录时发生错误: {str(e)}"
  916. logger.error(error_msg, exc_info=True)
  917. return jsonify({
  918. 'success': False,
  919. 'message': error_msg,
  920. 'data': None
  921. }), 500
  922. @bp.route('/delete-hotel-group-brands/<int:brand_id>', methods=['DELETE'])
  923. def delete_hotel_group_brands_route(brand_id):
  924. """
  925. 删除指定ID的酒店集团子品牌数据表记录的API接口
  926. 路径参数:
  927. - brand_id: 品牌记录ID
  928. 返回:
  929. - JSON: 包含删除结果和被删除的品牌信息
  930. """
  931. try:
  932. # 调用业务逻辑函数删除品牌记录
  933. result = delete_hotel_group_brands(brand_id)
  934. # 根据处理结果设置HTTP状态码
  935. if result['code'] == 200:
  936. status_code = 200 # OK
  937. elif result['code'] == 404:
  938. status_code = 404 # Not Found
  939. else:
  940. status_code = 500 # Internal Server Error
  941. return jsonify(result), status_code
  942. except Exception as e:
  943. # 处理未预期的异常
  944. error_msg = f"删除酒店集团品牌记录时发生错误: {str(e)}"
  945. logger.error(error_msg, exc_info=True)
  946. return jsonify({
  947. 'success': False,
  948. 'message': error_msg,
  949. 'data': None
  950. }), 500
  951. # ==================================
  952. # 重复记录处理API接口
  953. # ==================================
  954. @bp.route('/get-duplicate-records', methods=['GET'])
  955. def get_duplicate_records_route():
  956. """
  957. 获取重复记录列表的API接口
  958. 查询参数:
  959. - status: 可选,筛选特定状态的记录 ('pending', 'processed', 'ignored')
  960. 返回:
  961. - JSON: 包含重复记录列表和处理状态
  962. """
  963. try:
  964. # 获取查询参数
  965. status = request.args.get('status', None)
  966. # 验证status参数的有效性
  967. if status and status not in ['pending', 'processed', 'ignored']:
  968. return jsonify({
  969. 'success': False,
  970. 'message': 'status参数无效,必须为 pending、processed 或 ignored',
  971. 'data': None
  972. }), 400
  973. # 调用业务逻辑函数获取重复记录列表
  974. result = get_duplicate_records(status)
  975. # 根据处理结果设置HTTP状态码
  976. status_code = 200 if result['success'] else 500
  977. return jsonify(result), status_code
  978. except Exception as e:
  979. # 处理未预期的异常
  980. error_msg = f"获取重复记录列表时发生错误: {str(e)}"
  981. logger.error(error_msg, exc_info=True)
  982. return jsonify({
  983. 'success': False,
  984. 'message': error_msg,
  985. 'data': [],
  986. 'count': 0
  987. }), 500
  988. @bp.route('/process-duplicate-record/<int:duplicate_id>', methods=['POST'])
  989. def process_duplicate_record_route(duplicate_id):
  990. """
  991. 处理重复记录的API接口
  992. 路径参数:
  993. - duplicate_id: 重复记录ID
  994. 请求参数:
  995. - JSON格式,包含以下字段:
  996. - action: 处理动作 (必填) ('merge_to_suspected', 'keep_main', 'ignore')
  997. - selected_duplicate_id: 当action为'merge_to_suspected'时,选择的疑似重复记录ID (可选)
  998. - processed_by: 处理人 (可选)
  999. - notes: 处理备注 (可选)
  1000. 返回:
  1001. - JSON: 包含处理结果和状态信息
  1002. """
  1003. try:
  1004. # 获取请求数据
  1005. data = request.get_json()
  1006. if not data:
  1007. return jsonify({
  1008. 'success': False,
  1009. 'message': '请求数据为空',
  1010. 'data': None
  1011. }), 400
  1012. # 验证必填字段
  1013. action = data.get('action')
  1014. if not action:
  1015. return jsonify({
  1016. 'success': False,
  1017. 'message': '缺少必填字段: action',
  1018. 'data': None
  1019. }), 400
  1020. # 验证action参数的有效性
  1021. if action not in ['merge_to_suspected', 'keep_main', 'ignore']:
  1022. return jsonify({
  1023. 'success': False,
  1024. 'message': 'action参数无效,必须为 merge_to_suspected、keep_main 或 ignore',
  1025. 'data': None
  1026. }), 400
  1027. # 提取其他参数
  1028. selected_duplicate_id = data.get('selected_duplicate_id')
  1029. processed_by = data.get('processed_by')
  1030. notes = data.get('notes')
  1031. # 特殊验证:如果action为merge_to_suspected,必须提供selected_duplicate_id
  1032. if action == 'merge_to_suspected' and not selected_duplicate_id:
  1033. return jsonify({
  1034. 'success': False,
  1035. 'message': '执行merge_to_suspected操作时必须提供selected_duplicate_id',
  1036. 'data': None
  1037. }), 400
  1038. # 调用业务逻辑函数处理重复记录
  1039. result = process_duplicate_record(
  1040. duplicate_id=duplicate_id,
  1041. action=action,
  1042. selected_duplicate_id=selected_duplicate_id,
  1043. processed_by=processed_by,
  1044. notes=notes
  1045. )
  1046. # 根据处理结果设置HTTP状态码
  1047. if result['code'] == 200:
  1048. status_code = 200 # OK
  1049. elif result['code'] == 400:
  1050. status_code = 400 # Bad Request
  1051. elif result['code'] == 404:
  1052. status_code = 404 # Not Found
  1053. else:
  1054. status_code = 500 # Internal Server Error
  1055. return jsonify(result), status_code
  1056. except Exception as e:
  1057. # 处理未预期的异常
  1058. error_msg = f"处理重复记录时发生错误: {str(e)}"
  1059. logger.error(error_msg, exc_info=True)
  1060. return jsonify({
  1061. 'success': False,
  1062. 'message': error_msg,
  1063. 'data': None
  1064. }), 500
  1065. @bp.route('/get-duplicate-record-detail/<int:duplicate_id>', methods=['GET'])
  1066. def get_duplicate_record_detail_route(duplicate_id):
  1067. """
  1068. 获取指定重复记录详细信息的API接口
  1069. 路径参数:
  1070. - duplicate_id: 重复记录ID
  1071. 返回:
  1072. - JSON: 包含重复记录详细信息
  1073. """
  1074. try:
  1075. # 调用业务逻辑函数获取重复记录详情
  1076. result = get_duplicate_record_detail(duplicate_id)
  1077. # 根据处理结果设置HTTP状态码
  1078. if result['code'] == 200:
  1079. status_code = 200 # OK
  1080. elif result['code'] == 404:
  1081. status_code = 404 # Not Found
  1082. else:
  1083. status_code = 500 # Internal Server Error
  1084. return jsonify(result), status_code
  1085. except Exception as e:
  1086. # 处理未预期的异常
  1087. error_msg = f"获取重复记录详情时发生错误: {str(e)}"
  1088. logger.error(error_msg, exc_info=True)
  1089. return jsonify({
  1090. 'success': False,
  1091. 'message': error_msg,
  1092. 'data': None
  1093. }), 500