routes.py 50 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605
  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 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, search_business_cards_by_mobile, 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, fix_broken_duplicate_records
  4. # 导入新的名片图片解析函数和添加名片函数
  5. from app.core.data_parse.parse_card import process_business_card_image, add_business_card, delete_business_card
  6. # 导入网页文本解析函数
  7. from app.core.data_parse.parse_web import process_webpage_with_QWen
  8. from app.config.config import DevelopmentConfig, ProductionConfig
  9. import logging
  10. import boto3
  11. from botocore.config import Config
  12. from botocore.exceptions import ClientError
  13. from io import BytesIO
  14. import base64
  15. import os
  16. import urllib.parse
  17. from minio import Minio
  18. # Define logger
  19. logger = logging.getLogger(__name__)
  20. # For failure responses
  21. def failed(message, code=500):
  22. return {
  23. 'success': False,
  24. 'message': message,
  25. 'data': None
  26. }, code
  27. # 根据环境选择配置
  28. if os.environ.get('FLASK_ENV') == 'production':
  29. config = ProductionConfig()
  30. else:
  31. config = DevelopmentConfig()
  32. # 使用配置变量
  33. minio_url = f"{'https' if config.MINIO_SECURE else 'http'}://{config.MINIO_HOST}"
  34. minio_access_key = config.MINIO_USER
  35. minio_secret_key = config.MINIO_PASSWORD
  36. minio_bucket = config.MINIO_BUCKET
  37. use_ssl = config.MINIO_SECURE
  38. def get_minio_client():
  39. """获取 MinIO 客户端实例"""
  40. return Minio(
  41. '192.168.3.143:9000',
  42. access_key=config.MINIO_USER,
  43. secret_key=config.MINIO_PASSWORD,
  44. secure=config.MINIO_SECURE
  45. )
  46. # 名片解析接口
  47. @bp.route('/business-card-parse', methods=['POST'])
  48. def parse_business_card_route():
  49. """
  50. 解析名片图片并提取信息的API接口(仅解析,不保存到数据库)
  51. 请求参数:
  52. - image: 名片图片文件 (multipart/form-data)
  53. 返回:
  54. - JSON: 包含提取的名片信息和处理状态
  55. 注意:此接口仅负责图片解析和信息提取,不会将数据保存到数据库
  56. """
  57. # 检查是否上传了文件
  58. if 'image' not in request.files:
  59. return jsonify({
  60. 'success': False,
  61. 'message': '未上传图片',
  62. 'data': None
  63. }), 400
  64. image_file = request.files['image']
  65. # 检查文件是否为空
  66. if image_file.filename == '':
  67. return jsonify({
  68. 'success': False,
  69. 'message': '未选择文件',
  70. 'data': None
  71. }), 400
  72. # 检查文件类型是否为图片
  73. if not image_file.content_type.startswith('image/'):
  74. return jsonify({
  75. 'success': False,
  76. 'message': '上传的文件不是图片',
  77. 'data': None
  78. }), 400
  79. # 处理名片图片
  80. result = process_business_card_image(image_file)
  81. if result['success']:
  82. return jsonify(result), 200
  83. else:
  84. return jsonify(result), 500
  85. # 添加名片记录接口
  86. @bp.route('/add-business-card', methods=['POST'])
  87. def add_business_card_route():
  88. """
  89. 添加名片记录的API接口(解析图片并保存到数据库)
  90. 请求参数:
  91. - card_data: 名片信息数据 (JSON格式,可以通过form-data或JSON body传递)
  92. - image: 名片图片文件 (multipart/form-data,可选)
  93. 返回:
  94. - JSON: 包含保存结果和处理状态
  95. 注意:此接口负责业务逻辑处理,包括重复检查、MinIO上传和数据库保存
  96. """
  97. try:
  98. # 获取名片数据 - 支持两种方式
  99. card_data = None
  100. # 方式1:通过JSON body传递
  101. if request.is_json:
  102. card_data = request.get_json()
  103. # 方式2:通过form-data传递card_data字段
  104. elif 'card_data' in request.form:
  105. import json
  106. try:
  107. card_data = json.loads(request.form['card_data'])
  108. except json.JSONDecodeError:
  109. return jsonify({
  110. 'success': False,
  111. 'message': 'card_data格式错误,必须是有效的JSON字符串',
  112. 'data': None
  113. }), 400
  114. # 检查是否提供了名片数据
  115. if not card_data:
  116. return jsonify({
  117. 'success': False,
  118. 'message': '未提供名片数据,请通过JSON body或form-data的card_data字段传递',
  119. 'data': None
  120. }), 400
  121. # 获取可选的图片文件
  122. image_file = None
  123. if 'image' in request.files:
  124. image_file = request.files['image']
  125. # 检查文件是否为空
  126. if image_file.filename == '':
  127. image_file = None
  128. # 检查文件类型是否为图片
  129. elif not image_file.content_type.startswith('image/'):
  130. return jsonify({
  131. 'success': False,
  132. 'message': '上传的文件不是图片',
  133. 'data': None
  134. }), 400
  135. # 调用业务逻辑函数处理名片数据
  136. result = add_business_card(card_data, image_file)
  137. # 根据处理结果设置HTTP状态码
  138. if result['success']:
  139. if result['code'] == 200:
  140. status_code = 200
  141. elif result['code'] == 202:
  142. status_code = 202 # Accepted - 创建成功但有疑似重复记录
  143. else:
  144. status_code = 200
  145. else:
  146. if result['code'] == 400:
  147. status_code = 400
  148. else:
  149. status_code = 500
  150. return jsonify(result), status_code
  151. except Exception as e:
  152. logger.error(f"添加名片记录失败: {str(e)}")
  153. return jsonify({
  154. 'success': False,
  155. 'message': f'添加名片记录失败: {str(e)}',
  156. 'data': None
  157. }), 500
  158. # 更新名片信息接口
  159. @bp.route('/business-cards/<int:card_id>', methods=['PUT'])
  160. def update_business_card_route(card_id):
  161. """
  162. 更新名片信息的API接口
  163. 路径参数:
  164. - card_id: 名片记录ID
  165. 请求参数:
  166. - JSON格式的名片信息
  167. 返回:
  168. - JSON: 包含更新后的名片信息和处理状态
  169. """
  170. # 获取请求数据
  171. data = request.json
  172. if not data:
  173. return jsonify({
  174. 'success': False,
  175. 'message': '请求数据为空',
  176. 'data': None
  177. }), 400
  178. # 调用业务逻辑函数处理更新
  179. result = update_business_card(card_id, data)
  180. # 根据处理结果设置HTTP状态码
  181. status_code = 200 if result['success'] else 500
  182. if 'not found' in result.get('message', '').lower() or '未找到' in result.get('message', ''):
  183. status_code = 404
  184. return jsonify(result), status_code
  185. # 获取所有名片记录的API接口
  186. @bp.route('/get-business-cards', methods=['GET'])
  187. def get_business_cards_route():
  188. """
  189. 获取所有名片记录的API接口
  190. 返回:
  191. - JSON: 包含名片记录列表和处理状态
  192. """
  193. # 调用业务逻辑函数获取名片列表
  194. result = get_business_cards()
  195. # 根据处理结果设置HTTP状态码
  196. status_code = 200 if result['success'] else 500
  197. return jsonify(result), status_code
  198. @bp.route('/update-business-cards/<int:card_id>/status', methods=['PUT'])
  199. def update_business_card_status_route(card_id):
  200. """
  201. 更新名片状态的API接口
  202. 路径参数:
  203. - card_id: 名片记录ID
  204. 请求参数:
  205. - JSON格式,包含status字段
  206. 返回:
  207. - JSON: 包含更新后的名片信息和处理状态
  208. """
  209. # 获取请求数据
  210. data = request.json
  211. if not data or 'status' not in data:
  212. return jsonify({
  213. 'success': False,
  214. 'message': '请求数据为空或缺少status字段',
  215. 'data': None
  216. }), 400
  217. status = data['status']
  218. # 调用业务逻辑函数处理状态更新
  219. result = update_business_card_status(card_id, status)
  220. # 根据处理结果设置HTTP状态码
  221. status_code = 200 if result['success'] else 500
  222. if 'not found' in result.get('message', '').lower() or '未找到' in result.get('message', ''):
  223. status_code = 404
  224. return jsonify(result), status_code
  225. # 从MinIO获取名片图片的API接口
  226. @bp.route('/business-cards/image/<path:image_path>', methods=['GET'])
  227. def get_business_card_image(image_path):
  228. """
  229. 从MinIO获取名片图片的API接口
  230. 路径参数:
  231. - image_path: MinIO中的图片路径
  232. 返回:
  233. - 图片数据流
  234. """
  235. try:
  236. # 记录下载请求信息,便于调试
  237. logger.info(f"获取名片图片请求: {image_path}")
  238. # 获取 MinIO 客户端
  239. minio_client = get_minio_client()
  240. if not minio_client:
  241. return jsonify(failed("MinIO客户端初始化失败")), 500
  242. try:
  243. # 使用正确的MinIO客户端方法
  244. data = minio_client.get_object(minio_bucket, image_path)
  245. # 创建内存文件流
  246. file_stream = BytesIO(data.read())
  247. # 获取文件名
  248. file_name = image_path.split('/')[-1]
  249. # 返回文件
  250. return send_file(
  251. file_stream,
  252. as_attachment=False, # 设置为False,让浏览器直接显示图片
  253. download_name=file_name,
  254. mimetype='image/jpeg' # 根据实际图片类型设置
  255. )
  256. except Exception as e:
  257. logger.error(f"MinIO获取文件失败: {str(e)}")
  258. return jsonify(failed(f"文件获取失败: {str(e)}")), 404
  259. except Exception as e:
  260. logger.error(f"文件下载失败: {str(e)}")
  261. return jsonify(failed(str(e))), 500
  262. finally:
  263. # 确保关闭数据流
  264. if 'data' in locals():
  265. data.close()
  266. # 创建人才标签接口
  267. @bp.route('/create-talent-tag', methods=['POST'])
  268. def create_talent_tag_route():
  269. """
  270. 创建人才标签的API接口
  271. 请求参数:
  272. - JSON格式,包含以下字段:
  273. - name: 标签名称
  274. - category: 标签分类
  275. - description: 标签描述
  276. - status: 启用状态,默认为'active'
  277. 返回:
  278. - JSON: 包含创建结果和标签信息
  279. """
  280. try:
  281. # 获取请求数据
  282. data = request.get_json()
  283. if not data:
  284. return jsonify({
  285. 'success': False,
  286. 'message': '请求数据为空',
  287. 'data': None
  288. }), 400
  289. # 验证必要字段
  290. if 'name' not in data or not data['name']:
  291. return jsonify({
  292. 'success': False,
  293. 'message': '标签名称不能为空',
  294. 'data': None
  295. }), 400
  296. # 处理分类字段,如果未提供则设置默认值
  297. if 'category' not in data or not data['category']:
  298. data['category'] = '未分类'
  299. # 调用业务逻辑函数处理创建
  300. result = create_talent_tag(data)
  301. # 根据处理结果设置HTTP状态码
  302. status_code = 200 if result['success'] else 500
  303. return jsonify(result), status_code
  304. except Exception as e:
  305. logger.error(f"创建人才标签失败: {str(e)}")
  306. return jsonify({
  307. 'success': False,
  308. 'message': f'创建人才标签失败: {str(e)}',
  309. 'data': None
  310. }), 500
  311. # 获取人才标签列表接口
  312. @bp.route('/get-talent-tag-list', methods=['GET'])
  313. def get_talent_tag_list_route():
  314. """
  315. 获取人才标签列表的API接口
  316. 返回:
  317. - JSON: 包含人才标签列表和处理状态
  318. """
  319. try:
  320. # 调用业务逻辑函数获取人才标签列表
  321. result = get_talent_tag_list()
  322. # 根据处理结果设置HTTP状态码
  323. status_code = 200 if result['success'] else 500
  324. return jsonify(result), status_code
  325. except Exception as e:
  326. logger.error(f"获取人才标签列表失败: {str(e)}")
  327. return jsonify({
  328. 'success': False,
  329. 'message': f'获取人才标签列表失败: {str(e)}',
  330. 'data': []
  331. }), 500
  332. # 更新人才标签接口
  333. @bp.route('/update-talent-tag/<int:tag_id>', methods=['PUT'])
  334. def update_talent_tag_route(tag_id):
  335. """
  336. 更新人才标签的API接口
  337. 路径参数:
  338. - tag_id: 标签节点ID
  339. 请求参数:
  340. - JSON格式,可能包含以下字段:
  341. - name: 标签名称
  342. - category: 标签分类
  343. - description: 标签描述
  344. - status: 启用状态
  345. 返回:
  346. - JSON: 包含更新结果和标签信息
  347. """
  348. try:
  349. # 获取请求数据
  350. data = request.get_json()
  351. if not data:
  352. return jsonify({
  353. 'success': False,
  354. 'message': '请求数据为空',
  355. 'data': None
  356. }), 400
  357. # 调用业务逻辑函数处理更新
  358. result = update_talent_tag(tag_id, data)
  359. # 根据处理结果设置HTTP状态码
  360. if not result['success']:
  361. if result['code'] == 404:
  362. status_code = 404
  363. elif result['code'] == 400:
  364. status_code = 400
  365. else:
  366. status_code = 500
  367. else:
  368. status_code = 200
  369. return jsonify(result), status_code
  370. except Exception as e:
  371. logger.error(f"更新人才标签失败: {str(e)}")
  372. return jsonify({
  373. 'success': False,
  374. 'message': f'更新人才标签失败: {str(e)}',
  375. 'data': None
  376. }), 500
  377. # 删除人才标签接口
  378. @bp.route('/delete-talent-tag/<int:tag_id>', methods=['DELETE'])
  379. def delete_talent_tag_route(tag_id):
  380. """
  381. 删除人才标签的API接口
  382. 路径参数:
  383. - tag_id: 标签节点ID
  384. 返回:
  385. - JSON: 包含删除结果和被删除的标签信息
  386. """
  387. try:
  388. # 调用业务逻辑函数执行删除
  389. result = delete_talent_tag(tag_id)
  390. # 根据处理结果设置HTTP状态码
  391. if not result['success']:
  392. if result['code'] == 404:
  393. status_code = 404
  394. else:
  395. status_code = 500
  396. else:
  397. status_code = 200
  398. return jsonify(result), status_code
  399. except Exception as e:
  400. logger.error(f"删除人才标签失败: {str(e)}")
  401. return jsonify({
  402. 'success': False,
  403. 'message': f'删除人才标签失败: {str(e)}',
  404. 'data': None
  405. }), 500
  406. @bp.route('/query-kg', methods=['POST'])
  407. def query_kg():
  408. """
  409. 查询知识图谱API接口
  410. 请求参数:
  411. - query_requirement: 查询需求描述(JSON格式)
  412. 返回:
  413. - JSON: 包含查询结果和处理状态
  414. """
  415. try:
  416. # 获取请求数据
  417. data = request.json
  418. if not data or 'query_requirement' not in data:
  419. return jsonify({
  420. 'code': 400,
  421. 'success': False,
  422. 'message': '请求数据为空或缺少query_requirement字段',
  423. 'data': []
  424. }), 400
  425. query_requirement = data['query_requirement']
  426. # 调用业务逻辑函数执行查询
  427. result = query_neo4j_graph(query_requirement)
  428. # 根据处理结果设置HTTP状态码
  429. status_code = 200 if result['success'] else 500
  430. return jsonify(result), status_code
  431. except Exception as e:
  432. logger.error(f"查询知识图谱失败: {str(e)}")
  433. return jsonify({
  434. 'code': 500,
  435. 'success': False,
  436. 'message': f"查询知识图谱失败: {str(e)}",
  437. 'data': []
  438. }), 500
  439. @bp.route('/talent-get-tags/<int:talent_id>', methods=['GET'])
  440. def talent_get_tags_route(talent_id):
  441. """
  442. 获取人才标签的API接口
  443. 路径参数:
  444. - talent_id: 人才节点ID
  445. 返回:
  446. - JSON: 包含人才关联的标签列表和处理状态
  447. """
  448. try:
  449. # 调用业务逻辑函数获取人才标签
  450. result = talent_get_tags(talent_id)
  451. # 根据处理结果设置HTTP状态码
  452. status_code = 200 if result['success'] else 500
  453. return jsonify(result), status_code
  454. except Exception as e:
  455. logger.error(f"获取人才标签失败: {str(e)}")
  456. return jsonify({
  457. 'code': 500,
  458. 'success': False,
  459. 'message': f"获取人才标签失败: {str(e)}",
  460. 'data': []
  461. }), 500
  462. @bp.route('/talent-update-tags', methods=['POST'])
  463. def talent_update_tags_route():
  464. """
  465. 更新人才标签关系的API接口
  466. 请求参数:
  467. - JSON数组,包含talent和tag字段的对象列表
  468. 例如: [
  469. {"talent": 12345, "tag": "市场营销"},
  470. {"talent": 12345, "tag": "酒店管理"}
  471. ]
  472. 返回:
  473. - JSON: 包含更新结果的状态信息
  474. """
  475. try:
  476. # 获取请求数据
  477. data = request.json
  478. if not data:
  479. return jsonify({
  480. 'code': 400,
  481. 'success': False,
  482. 'message': '请求数据为空',
  483. 'data': None
  484. }), 400
  485. # 调用业务逻辑函数处理标签关系更新
  486. result = talent_update_tags(data)
  487. # 根据处理结果设置HTTP状态码
  488. if result['code'] == 200:
  489. status_code = 200
  490. elif result['code'] == 206:
  491. status_code = 206 # Partial Content
  492. elif result['code'] == 400:
  493. status_code = 400 # Bad Request
  494. elif result['code'] == 404:
  495. status_code = 404 # Not Found
  496. else:
  497. status_code = 500 # Internal Server Error
  498. return jsonify(result), status_code
  499. except Exception as e:
  500. logger.error(f"更新人才标签关系失败: {str(e)}")
  501. return jsonify({
  502. 'code': 500,
  503. 'success': False,
  504. 'message': f"更新人才标签关系失败: {str(e)}",
  505. 'data': None
  506. }), 500
  507. # 测试MinIO连接
  508. def test_minio_connection():
  509. """测试MinIO连接是否正常"""
  510. try:
  511. client = get_minio_client()
  512. if client.bucket_exists(minio_bucket):
  513. return {
  514. 'success': True,
  515. 'message': f'连接MinIO服务器成功,存储桶 {minio_bucket} 存在',
  516. 'config': {
  517. 'host': config.MINIO_HOST,
  518. 'bucket': minio_bucket,
  519. 'secure': use_ssl
  520. }
  521. }
  522. else:
  523. return {
  524. 'success': False,
  525. 'message': f'连接MinIO服务器成功,但存储桶 {minio_bucket} 不存在',
  526. 'config': {
  527. 'host': config.MINIO_HOST,
  528. 'bucket': minio_bucket,
  529. 'secure': use_ssl
  530. }
  531. }
  532. except Exception as e:
  533. return {
  534. 'success': False,
  535. 'message': f'连接MinIO服务器失败: {str(e)}',
  536. 'config': {
  537. 'host': config.MINIO_HOST,
  538. 'bucket': minio_bucket,
  539. 'secure': use_ssl
  540. }
  541. }
  542. # MinIO测试接口
  543. @bp.route('/test-minio-connection', methods=['GET'])
  544. def test_minio_connection_route():
  545. """
  546. 测试MinIO连接的API接口
  547. 返回:
  548. - JSON: 包含连接测试结果
  549. """
  550. try:
  551. result = test_minio_connection()
  552. status_code = 200 if result['success'] else 500
  553. return jsonify(result), status_code
  554. except Exception as e:
  555. logger.error(f"测试MinIO连接失败: {str(e)}")
  556. return jsonify({
  557. 'success': False,
  558. 'message': f'测试MinIO连接失败: {str(e)}',
  559. 'config': {
  560. 'host': config.MINIO_HOST,
  561. 'bucket': minio_bucket,
  562. 'secure': use_ssl
  563. }
  564. }), 500
  565. # 获取单个名片记录的API接口
  566. @bp.route('/get-business-card/<int:card_id>', methods=['GET'])
  567. def get_business_card_route(card_id):
  568. """
  569. 获取单个名片记录的API接口
  570. 路径参数:
  571. - card_id: 名片记录ID
  572. 返回:
  573. - JSON: 包含名片记录信息和处理状态
  574. """
  575. # 调用业务逻辑函数获取名片记录
  576. result = get_business_card(card_id)
  577. # 根据处理结果设置HTTP状态码
  578. if not result['success']:
  579. if result['code'] == 404:
  580. status_code = 404
  581. else:
  582. status_code = 500
  583. else:
  584. status_code = 200
  585. return jsonify(result), status_code
  586. @bp.route('/search-business-cards-by-mobile', methods=['GET'])
  587. def search_business_cards_by_mobile_route():
  588. """
  589. 根据手机号码搜索名片记录的API接口
  590. 查询参数:
  591. - mobile: 要搜索的手机号码
  592. 返回:
  593. - JSON: 包含搜索到的名片记录列表和处理状态
  594. 示例:
  595. GET /search-business-cards-by-mobile?mobile=13800138000
  596. """
  597. try:
  598. # 获取查询参数
  599. mobile_number = request.args.get('mobile', '').strip()
  600. if not mobile_number:
  601. return jsonify({
  602. 'success': False,
  603. 'message': '请提供要搜索的手机号码',
  604. 'data': []
  605. }), 400
  606. # 调用业务逻辑函数搜索名片记录
  607. result = search_business_cards_by_mobile(mobile_number)
  608. # 根据处理结果设置HTTP状态码
  609. if result['code'] == 200:
  610. status_code = 200
  611. elif result['code'] == 400:
  612. status_code = 400
  613. else:
  614. status_code = 500
  615. return jsonify(result), status_code
  616. except Exception as e:
  617. # 处理未预期的异常
  618. error_msg = f"根据手机号码搜索名片时发生错误: {str(e)}"
  619. logger.error(error_msg, exc_info=True)
  620. return jsonify({
  621. 'success': False,
  622. 'message': error_msg,
  623. 'data': []
  624. }), 500
  625. @bp.route('/get-hotel-positions-list', methods=['GET'])
  626. def get_hotel_positions_list_route():
  627. """
  628. 获取酒店职位数据表全部记录的API接口
  629. 返回:
  630. - JSON: 包含酒店职位记录列表和处理状态
  631. """
  632. try:
  633. # 调用业务逻辑函数获取酒店职位列表
  634. result = get_hotel_positions_list()
  635. # 根据处理结果设置HTTP状态码
  636. status_code = 200 if result['success'] else 500
  637. return jsonify(result), status_code
  638. except Exception as e:
  639. # 处理未预期的异常
  640. error_msg = f"获取酒店职位列表时发生错误: {str(e)}"
  641. logger.error(error_msg, exc_info=True)
  642. return jsonify({
  643. 'success': False,
  644. 'message': error_msg,
  645. 'data': [],
  646. 'count': 0
  647. }), 500
  648. @bp.route('/add-hotel-positions', methods=['POST'])
  649. def add_hotel_positions_route():
  650. """
  651. 新增酒店职位数据表记录的API接口
  652. 请求参数:
  653. - JSON格式,包含以下字段:
  654. - department_zh: 部门中文名称 (必填)
  655. - department_en: 部门英文名称 (必填)
  656. - position_zh: 职位中文名称 (必填)
  657. - position_en: 职位英文名称 (必填)
  658. - position_abbr: 职位英文缩写 (可选)
  659. - level_zh: 职级中文名称 (必填)
  660. - level_en: 职级英文名称 (必填)
  661. - created_by: 创建者 (可选)
  662. - updated_by: 更新者 (可选)
  663. - status: 状态 (可选)
  664. 返回:
  665. - JSON: 包含创建结果和职位信息
  666. """
  667. try:
  668. # 获取请求数据
  669. data = request.get_json()
  670. if not data:
  671. return jsonify({
  672. 'success': False,
  673. 'message': '请求数据为空',
  674. 'data': None
  675. }), 400
  676. # 调用业务逻辑函数处理创建
  677. result = add_hotel_positions(data)
  678. # 根据处理结果设置HTTP状态码
  679. if result['code'] == 200:
  680. status_code = 201 # Created
  681. elif result['code'] == 400:
  682. status_code = 400 # Bad Request
  683. elif result['code'] == 409:
  684. status_code = 409 # Conflict
  685. else:
  686. status_code = 500 # Internal Server Error
  687. return jsonify(result), status_code
  688. except Exception as e:
  689. # 处理未预期的异常
  690. error_msg = f"创建酒店职位记录时发生错误: {str(e)}"
  691. logger.error(error_msg, exc_info=True)
  692. return jsonify({
  693. 'success': False,
  694. 'message': error_msg,
  695. 'data': None
  696. }), 500
  697. @bp.route('/update-hotel-positions/<int:position_id>', methods=['PUT'])
  698. def update_hotel_positions_route(position_id):
  699. """
  700. 修改酒店职位数据表记录的API接口
  701. 路径参数:
  702. - position_id: 职位记录ID
  703. 请求参数:
  704. - JSON格式,可能包含以下字段:
  705. - department_zh: 部门中文名称
  706. - department_en: 部门英文名称
  707. - position_zh: 职位中文名称
  708. - position_en: 职位英文名称
  709. - position_abbr: 职位英文缩写
  710. - level_zh: 职级中文名称
  711. - level_en: 职级英文名称
  712. - updated_by: 更新者
  713. - status: 状态
  714. 返回:
  715. - JSON: 包含更新结果和职位信息
  716. """
  717. try:
  718. # 获取请求数据
  719. data = request.get_json()
  720. if not data:
  721. return jsonify({
  722. 'success': False,
  723. 'message': '请求数据为空',
  724. 'data': None
  725. }), 400
  726. # 调用业务逻辑函数处理更新
  727. result = update_hotel_positions(position_id, data)
  728. # 根据处理结果设置HTTP状态码
  729. if result['code'] == 200:
  730. status_code = 200 # OK
  731. elif result['code'] == 400:
  732. status_code = 400 # Bad Request
  733. elif result['code'] == 404:
  734. status_code = 404 # Not Found
  735. elif result['code'] == 409:
  736. status_code = 409 # Conflict
  737. else:
  738. status_code = 500 # Internal Server Error
  739. return jsonify(result), status_code
  740. except Exception as e:
  741. # 处理未预期的异常
  742. error_msg = f"更新酒店职位记录时发生错误: {str(e)}"
  743. logger.error(error_msg, exc_info=True)
  744. return jsonify({
  745. 'success': False,
  746. 'message': error_msg,
  747. 'data': None
  748. }), 500
  749. @bp.route('/query-hotel-positions/<int:position_id>', methods=['GET'])
  750. def query_hotel_positions_route(position_id):
  751. """
  752. 查找指定ID的酒店职位数据表记录的API接口
  753. 路径参数:
  754. - position_id: 职位记录ID
  755. 返回:
  756. - JSON: 包含查找结果和职位信息
  757. """
  758. try:
  759. # 调用业务逻辑函数查找职位记录
  760. result = query_hotel_positions(position_id)
  761. # 根据处理结果设置HTTP状态码
  762. if result['code'] == 200:
  763. status_code = 200 # OK
  764. elif result['code'] == 404:
  765. status_code = 404 # Not Found
  766. else:
  767. status_code = 500 # Internal Server Error
  768. return jsonify(result), status_code
  769. except Exception as e:
  770. # 处理未预期的异常
  771. error_msg = f"查找酒店职位记录时发生错误: {str(e)}"
  772. logger.error(error_msg, exc_info=True)
  773. return jsonify({
  774. 'success': False,
  775. 'message': error_msg,
  776. 'data': None
  777. }), 500
  778. @bp.route('/delete-hotel-positions/<int:position_id>', methods=['DELETE'])
  779. def delete_hotel_positions_route(position_id):
  780. """
  781. 删除指定ID的酒店职位数据表记录的API接口
  782. 路径参数:
  783. - position_id: 职位记录ID
  784. 返回:
  785. - JSON: 包含删除结果和被删除的职位信息
  786. """
  787. try:
  788. # 调用业务逻辑函数删除职位记录
  789. result = delete_hotel_positions(position_id)
  790. # 根据处理结果设置HTTP状态码
  791. if result['code'] == 200:
  792. status_code = 200 # OK
  793. elif result['code'] == 404:
  794. status_code = 404 # Not Found
  795. else:
  796. status_code = 500 # Internal Server Error
  797. return jsonify(result), status_code
  798. except Exception as e:
  799. # 处理未预期的异常
  800. error_msg = f"删除酒店职位记录时发生错误: {str(e)}"
  801. logger.error(error_msg, exc_info=True)
  802. return jsonify({
  803. 'success': False,
  804. 'message': error_msg,
  805. 'data': None
  806. }), 500
  807. @bp.route('/get-hotel-group-brands-list', methods=['GET'])
  808. def get_hotel_group_brands_list_route():
  809. """
  810. 获取酒店集团子品牌数据表全部记录的API接口
  811. 返回:
  812. - JSON: 包含酒店集团品牌记录列表和处理状态
  813. """
  814. try:
  815. # 调用业务逻辑函数获取酒店集团品牌列表
  816. result = get_hotel_group_brands_list()
  817. # 根据处理结果设置HTTP状态码
  818. status_code = 200 if result['success'] else 500
  819. return jsonify(result), status_code
  820. except Exception as e:
  821. # 处理未预期的异常
  822. error_msg = f"获取酒店集团品牌列表时发生错误: {str(e)}"
  823. logger.error(error_msg, exc_info=True)
  824. return jsonify({
  825. 'success': False,
  826. 'message': error_msg,
  827. 'data': [],
  828. 'count': 0
  829. }), 500
  830. @bp.route('/add-hotel-group-brands', methods=['POST'])
  831. def add_hotel_group_brands_route():
  832. """
  833. 新增酒店集团子品牌数据表记录的API接口
  834. 请求参数:
  835. - JSON格式,包含以下字段:
  836. - group_name_en: 集团英文名称 (必填)
  837. - group_name_zh: 集团中文名称 (必填)
  838. - brand_name_en: 品牌英文名称 (必填)
  839. - brand_name_zh: 品牌中文名称 (必填)
  840. - positioning_level_en: 定位级别英文名称 (必填)
  841. - positioning_level_zh: 定位级别中文名称 (必填)
  842. - created_by: 创建者 (可选)
  843. - updated_by: 更新者 (可选)
  844. - status: 状态 (可选)
  845. 返回:
  846. - JSON: 包含创建结果和品牌信息
  847. """
  848. try:
  849. # 获取请求数据
  850. data = request.get_json()
  851. if not data:
  852. return jsonify({
  853. 'success': False,
  854. 'message': '请求数据为空',
  855. 'data': None
  856. }), 400
  857. # 调用业务逻辑函数处理创建
  858. result = add_hotel_group_brands(data)
  859. # 根据处理结果设置HTTP状态码
  860. if result['code'] == 200:
  861. status_code = 201 # Created
  862. elif result['code'] == 400:
  863. status_code = 400 # Bad Request
  864. elif result['code'] == 409:
  865. status_code = 409 # Conflict
  866. else:
  867. status_code = 500 # Internal Server Error
  868. return jsonify(result), status_code
  869. except Exception as e:
  870. # 处理未预期的异常
  871. error_msg = f"创建酒店集团品牌记录时发生错误: {str(e)}"
  872. logger.error(error_msg, exc_info=True)
  873. return jsonify({
  874. 'success': False,
  875. 'message': error_msg,
  876. 'data': None
  877. }), 500
  878. @bp.route('/update-hotel-group-brands/<int:brand_id>', methods=['PUT'])
  879. def update_hotel_group_brands_route(brand_id):
  880. """
  881. 修改酒店集团子品牌数据表记录的API接口
  882. 路径参数:
  883. - brand_id: 品牌记录ID
  884. 请求参数:
  885. - JSON格式,可能包含以下字段:
  886. - group_name_en: 集团英文名称
  887. - group_name_zh: 集团中文名称
  888. - brand_name_en: 品牌英文名称
  889. - brand_name_zh: 品牌中文名称
  890. - positioning_level_en: 定位级别英文名称
  891. - positioning_level_zh: 定位级别中文名称
  892. - updated_by: 更新者
  893. - status: 状态
  894. 返回:
  895. - JSON: 包含更新结果和品牌信息
  896. """
  897. try:
  898. # 获取请求数据
  899. data = request.get_json()
  900. if not data:
  901. return jsonify({
  902. 'success': False,
  903. 'message': '请求数据为空',
  904. 'data': None
  905. }), 400
  906. # 调用业务逻辑函数处理更新
  907. result = update_hotel_group_brands(brand_id, data)
  908. # 根据处理结果设置HTTP状态码
  909. if result['code'] == 200:
  910. status_code = 200 # OK
  911. elif result['code'] == 400:
  912. status_code = 400 # Bad Request
  913. elif result['code'] == 404:
  914. status_code = 404 # Not Found
  915. elif result['code'] == 409:
  916. status_code = 409 # Conflict
  917. else:
  918. status_code = 500 # Internal Server Error
  919. return jsonify(result), status_code
  920. except Exception as e:
  921. # 处理未预期的异常
  922. error_msg = f"更新酒店集团品牌记录时发生错误: {str(e)}"
  923. logger.error(error_msg, exc_info=True)
  924. return jsonify({
  925. 'success': False,
  926. 'message': error_msg,
  927. 'data': None
  928. }), 500
  929. @bp.route('/query-hotel-group-brands/<int:brand_id>', methods=['GET'])
  930. def query_hotel_group_brands_route(brand_id):
  931. """
  932. 查找指定ID的酒店集团子品牌数据表记录的API接口
  933. 路径参数:
  934. - brand_id: 品牌记录ID
  935. 返回:
  936. - JSON: 包含查找结果和品牌信息
  937. """
  938. try:
  939. # 调用业务逻辑函数查找品牌记录
  940. result = query_hotel_group_brands(brand_id)
  941. # 根据处理结果设置HTTP状态码
  942. if result['code'] == 200:
  943. status_code = 200 # OK
  944. elif result['code'] == 404:
  945. status_code = 404 # Not Found
  946. else:
  947. status_code = 500 # Internal Server Error
  948. return jsonify(result), status_code
  949. except Exception as e:
  950. # 处理未预期的异常
  951. error_msg = f"查找酒店集团品牌记录时发生错误: {str(e)}"
  952. logger.error(error_msg, exc_info=True)
  953. return jsonify({
  954. 'success': False,
  955. 'message': error_msg,
  956. 'data': None
  957. }), 500
  958. @bp.route('/delete-hotel-group-brands/<int:brand_id>', methods=['DELETE'])
  959. def delete_hotel_group_brands_route(brand_id):
  960. """
  961. 删除指定ID的酒店集团子品牌数据表记录的API接口
  962. 路径参数:
  963. - brand_id: 品牌记录ID
  964. 返回:
  965. - JSON: 包含删除结果和被删除的品牌信息
  966. """
  967. try:
  968. # 调用业务逻辑函数删除品牌记录
  969. result = delete_hotel_group_brands(brand_id)
  970. # 根据处理结果设置HTTP状态码
  971. if result['code'] == 200:
  972. status_code = 200 # OK
  973. elif result['code'] == 404:
  974. status_code = 404 # Not Found
  975. else:
  976. status_code = 500 # Internal Server Error
  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': None
  986. }), 500
  987. # ==================================
  988. # 重复记录处理API接口
  989. # ==================================
  990. @bp.route('/get-duplicate-records', methods=['GET'])
  991. def get_duplicate_records_route():
  992. """
  993. 获取重复记录列表的API接口
  994. 查询参数:
  995. - status: 可选,筛选特定状态的记录 ('pending', 'processed', 'ignored')
  996. 返回:
  997. - JSON: 包含重复记录列表和处理状态
  998. """
  999. try:
  1000. # 获取查询参数
  1001. status = request.args.get('status', None)
  1002. # 验证status参数的有效性
  1003. if status and status not in ['pending', 'processed', 'ignored']:
  1004. return jsonify({
  1005. 'success': False,
  1006. 'message': 'status参数无效,必须为 pending、processed 或 ignored',
  1007. 'data': None
  1008. }), 400
  1009. # 调用业务逻辑函数获取重复记录列表
  1010. result = get_duplicate_records(status)
  1011. # 根据处理结果设置HTTP状态码
  1012. status_code = 200 if result['success'] else 500
  1013. return jsonify(result), status_code
  1014. except Exception as e:
  1015. # 处理未预期的异常
  1016. error_msg = f"获取重复记录列表时发生错误: {str(e)}"
  1017. logger.error(error_msg, exc_info=True)
  1018. return jsonify({
  1019. 'success': False,
  1020. 'message': error_msg,
  1021. 'data': [],
  1022. 'count': 0
  1023. }), 500
  1024. @bp.route('/process-duplicate-record/<int:duplicate_id>', methods=['POST'])
  1025. def process_duplicate_record_route(duplicate_id):
  1026. """
  1027. 处理重复记录的API接口
  1028. 路径参数:
  1029. - duplicate_id: 重复记录ID
  1030. 请求参数:
  1031. - JSON格式,包含以下字段:
  1032. - action: 处理动作 (必填) ('merge_to_suspected', 'keep_main', 'ignore')
  1033. - selected_duplicate_id: 当action为'merge_to_suspected'时,选择的疑似重复记录ID (可选)
  1034. - processed_by: 处理人 (可选)
  1035. - notes: 处理备注 (可选)
  1036. 返回:
  1037. - JSON: 包含处理结果和状态信息
  1038. """
  1039. try:
  1040. # 获取请求数据
  1041. data = request.get_json()
  1042. if not data:
  1043. return jsonify({
  1044. 'success': False,
  1045. 'message': '请求数据为空',
  1046. 'data': None
  1047. }), 400
  1048. # 验证必填字段
  1049. action = data.get('action')
  1050. if not action:
  1051. return jsonify({
  1052. 'success': False,
  1053. 'message': '缺少必填字段: action',
  1054. 'data': None
  1055. }), 400
  1056. # 验证action参数的有效性
  1057. if action not in ['merge_to_suspected', 'keep_main', 'ignore']:
  1058. return jsonify({
  1059. 'success': False,
  1060. 'message': 'action参数无效,必须为 merge_to_suspected、keep_main 或 ignore',
  1061. 'data': None
  1062. }), 400
  1063. # 提取其他参数
  1064. selected_duplicate_id = data.get('selected_duplicate_id')
  1065. processed_by = data.get('processed_by')
  1066. notes = data.get('notes')
  1067. # 特殊验证:如果action为merge_to_suspected,必须提供selected_duplicate_id
  1068. if action == 'merge_to_suspected' and not selected_duplicate_id:
  1069. return jsonify({
  1070. 'success': False,
  1071. 'message': '执行merge_to_suspected操作时必须提供selected_duplicate_id',
  1072. 'data': None
  1073. }), 400
  1074. # 调用业务逻辑函数处理重复记录
  1075. result = process_duplicate_record(
  1076. duplicate_id=duplicate_id,
  1077. action=action,
  1078. selected_duplicate_id=selected_duplicate_id,
  1079. processed_by=processed_by,
  1080. notes=notes
  1081. )
  1082. # 根据处理结果设置HTTP状态码
  1083. if result['code'] == 200:
  1084. status_code = 200 # OK
  1085. elif result['code'] == 400:
  1086. status_code = 400 # Bad Request
  1087. elif result['code'] == 404:
  1088. status_code = 404 # Not Found
  1089. else:
  1090. status_code = 500 # Internal Server Error
  1091. return jsonify(result), status_code
  1092. except Exception as e:
  1093. # 处理未预期的异常
  1094. error_msg = f"处理重复记录时发生错误: {str(e)}"
  1095. logger.error(error_msg, exc_info=True)
  1096. return jsonify({
  1097. 'success': False,
  1098. 'message': error_msg,
  1099. 'data': None
  1100. }), 500
  1101. @bp.route('/get-duplicate-record-detail/<int:duplicate_id>', methods=['GET'])
  1102. def get_duplicate_record_detail_route(duplicate_id):
  1103. """
  1104. 获取指定重复记录详细信息的API接口
  1105. 路径参数:
  1106. - duplicate_id: 重复记录ID
  1107. 返回:
  1108. - JSON: 包含重复记录详细信息
  1109. """
  1110. try:
  1111. # 调用业务逻辑函数获取重复记录详情
  1112. result = get_duplicate_record_detail(duplicate_id)
  1113. # 根据处理结果设置HTTP状态码
  1114. if result['code'] == 200:
  1115. status_code = 200 # OK
  1116. elif result['code'] == 404:
  1117. status_code = 404 # Not Found
  1118. else:
  1119. status_code = 500 # Internal Server Error
  1120. return jsonify(result), status_code
  1121. except Exception as e:
  1122. # 处理未预期的异常
  1123. error_msg = f"获取重复记录详情时发生错误: {str(e)}"
  1124. logger.error(error_msg, exc_info=True)
  1125. return jsonify({
  1126. 'success': False,
  1127. 'message': error_msg,
  1128. 'data': None
  1129. }), 500
  1130. # 删除名片记录接口
  1131. @bp.route('/delete-business-card/<int:card_id>', methods=['DELETE'])
  1132. def delete_business_card_route(card_id):
  1133. """
  1134. 删除名片记录的API接口
  1135. 路径参数:
  1136. - card_id: 名片记录ID (必填)
  1137. 功能说明:
  1138. - 删除PostgreSQL数据库中business_cards表的指定记录
  1139. - 删除PostgreSQL数据库中duplicate_business_cards表的相关记录
  1140. - 删除MinIO存储中的名片图片文件
  1141. - 删除Neo4j图数据库中talent节点及其关联关系
  1142. 返回:
  1143. - JSON: 包含删除操作的结果状态和被删除的记录信息
  1144. 状态码:
  1145. - 200: 完全成功删除所有相关数据
  1146. - 206: 部分成功 (PostgreSQL删除成功,但Neo4j删除失败)
  1147. - 404: 未找到指定ID的名片记录
  1148. - 500: 删除操作失败
  1149. """
  1150. try:
  1151. # 验证card_id参数
  1152. if not card_id or card_id <= 0:
  1153. return jsonify({
  1154. 'success': False,
  1155. 'message': '无效的名片记录ID',
  1156. 'data': None
  1157. }), 400
  1158. # 调用删除函数
  1159. result = delete_business_card(card_id)
  1160. # 根据处理结果设置HTTP状态码和返回响应
  1161. if result['success']:
  1162. if result['code'] == 200:
  1163. status_code = 200 # 完全成功
  1164. elif result['code'] == 206:
  1165. status_code = 206 # 部分成功
  1166. else:
  1167. status_code = 200 # 默认成功
  1168. else:
  1169. if result['code'] == 404:
  1170. status_code = 404 # 未找到记录
  1171. elif result['code'] == 400:
  1172. status_code = 400 # 参数错误
  1173. else:
  1174. status_code = 500 # 服务器错误
  1175. return jsonify(result), status_code
  1176. except Exception as e:
  1177. logger.error(f"删除名片记录失败: {str(e)}")
  1178. return jsonify({
  1179. 'success': False,
  1180. 'message': f'删除名片记录失败: {str(e)}',
  1181. 'data': None
  1182. }), 500
  1183. # 修复损坏的重复记录接口
  1184. @bp.route('/fix-broken-duplicate-records', methods=['POST'])
  1185. def fix_broken_duplicate_records_route():
  1186. """
  1187. 修复duplicate_business_cards表中main_card_id为null的损坏记录
  1188. 功能说明:
  1189. - 查找所有main_card_id为null的损坏记录
  1190. - 删除这些损坏的记录以维护数据完整性
  1191. - 返回修复操作的详细结果
  1192. 返回:
  1193. - JSON: 包含修复操作的结果和被删除记录的信息
  1194. 状态码:
  1195. - 200: 修复成功
  1196. - 500: 修复失败
  1197. 注意:
  1198. - 此操作会永久删除损坏的记录
  1199. - 建议在系统维护时执行此操作
  1200. """
  1201. try:
  1202. # 调用修复函数
  1203. result = fix_broken_duplicate_records()
  1204. # 根据结果设置状态码
  1205. if result['success']:
  1206. status_code = 200
  1207. else:
  1208. status_code = 500
  1209. return jsonify(result), status_code
  1210. except Exception as e:
  1211. logger.error(f"修复损坏记录接口调用失败: {str(e)}")
  1212. return jsonify({
  1213. 'success': False,
  1214. 'message': f'修复损坏记录接口调用失败: {str(e)}',
  1215. 'data': None
  1216. }), 500
  1217. # 网页文本解析接口
  1218. @bp.route('/webpage-parse', methods=['POST'])
  1219. def webpage_parse_route():
  1220. """
  1221. 解析网页 Markdown 文本并提取人员信息的API接口
  1222. 请求参数:
  1223. - markdown_text: 网页的 Markdown 格式文本内容 (JSON格式)
  1224. - publish_time: 发布时间,用于career_path中的date字段 (JSON格式)
  1225. 请求体示例:
  1226. {
  1227. "markdown_text": "# 张三\n\n职位:高级经理\n\n公司:XX酒店\n\n![照片](http://example.com/photo.jpg)",
  1228. "publish_time": "2025-01-15"
  1229. }
  1230. 返回:
  1231. - JSON: 包含提取的人员信息和处理状态
  1232. 功能说明:
  1233. - 接收 Markdown 格式的网页文本
  1234. - 进行必要的格式和内容验证
  1235. - 使用 Qwen VL Max 模型提取人员信息
  1236. - 支持提取照片链接 (pic_url)
  1237. - 返回标准化的人员信息数据
  1238. 状态码:
  1239. - 200: 解析成功
  1240. - 400: 请求参数错误
  1241. - 500: 解析失败
  1242. """
  1243. try:
  1244. # 检查请求是否为 JSON 格式
  1245. if not request.is_json:
  1246. return jsonify({
  1247. 'success': False,
  1248. 'message': '请求必须是 JSON 格式',
  1249. 'data': None
  1250. }), 400
  1251. # 获取请求数据
  1252. data = request.get_json()
  1253. # 检查是否提供了 markdown_text 参数
  1254. if 'markdown_text' not in data:
  1255. return jsonify({
  1256. 'success': False,
  1257. 'message': '缺少必填参数: markdown_text',
  1258. 'data': None
  1259. }), 400
  1260. # 检查是否提供了 publish_time 参数
  1261. if 'publish_time' not in data:
  1262. return jsonify({
  1263. 'success': False,
  1264. 'message': '缺少必填参数: publish_time',
  1265. 'data': None
  1266. }), 400
  1267. markdown_text = data['markdown_text']
  1268. publish_time = data['publish_time']
  1269. # 验证 markdown_text 是否为字符串
  1270. if not isinstance(markdown_text, str):
  1271. return jsonify({
  1272. 'success': False,
  1273. 'message': 'markdown_text 必须是字符串类型',
  1274. 'data': None
  1275. }), 400
  1276. # 验证 publish_time 是否为字符串
  1277. if not isinstance(publish_time, str):
  1278. return jsonify({
  1279. 'success': False,
  1280. 'message': 'publish_time 必须是字符串类型',
  1281. 'data': None
  1282. }), 400
  1283. # 验证文本内容不能为空
  1284. if not markdown_text.strip():
  1285. return jsonify({
  1286. 'success': False,
  1287. 'message': 'markdown_text 内容不能为空',
  1288. 'data': None
  1289. }), 400
  1290. # 验证文本长度(防止过长的文本)
  1291. if len(markdown_text) > 50000: # 限制最大50KB
  1292. return jsonify({
  1293. 'success': False,
  1294. 'message': 'markdown_text 内容过长,最大支持50KB',
  1295. 'data': None
  1296. }), 400
  1297. # 基本的 Markdown 格式验证(可选)
  1298. # 检查是否包含一些基本的文本内容
  1299. if len(markdown_text.strip()) < 10:
  1300. return jsonify({
  1301. 'success': False,
  1302. 'message': 'markdown_text 内容过短,无法进行有效解析',
  1303. 'data': None
  1304. }), 400
  1305. # 记录解析请求
  1306. logger.info(f"开始解析网页文本,内容长度: {len(markdown_text)} 字符")
  1307. # 调用网页文本解析函数
  1308. extracted_data = process_webpage_with_QWen(markdown_text, publish_time)
  1309. # 返回成功结果
  1310. return jsonify({
  1311. 'success': True,
  1312. 'message': '网页文本解析成功',
  1313. 'data': extracted_data
  1314. }), 200
  1315. except Exception as e:
  1316. # 记录错误日志
  1317. error_msg = f"网页文本解析失败: {str(e)}"
  1318. logger.error(error_msg, exc_info=True)
  1319. # 返回错误响应
  1320. return jsonify({
  1321. 'success': False,
  1322. 'message': error_msg,
  1323. 'data': None
  1324. }), 500