routes.py 76 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373
  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_system import (
  4. update_business_card,
  5. get_business_cards,
  6. update_business_card_status,
  7. create_talent_tag,
  8. get_talent_tag_list,
  9. update_talent_tag,
  10. delete_talent_tag,
  11. query_neo4j_graph,
  12. talent_get_tags,
  13. talent_update_tags,
  14. get_business_card,
  15. search_business_cards_by_mobile,
  16. get_duplicate_records,
  17. process_duplicate_record,
  18. get_duplicate_record_detail,
  19. fix_broken_duplicate_records
  20. )
  21. # 导入解析任务相关函数
  22. from app.core.data_parse.parse_task import (
  23. get_parse_tasks,
  24. get_parse_task_detail,
  25. add_parse_task,
  26. add_parsed_talents
  27. )
  28. # 导入酒店管理相关函数
  29. from app.core.data_parse.hotel_management import (
  30. get_hotel_positions_list,
  31. add_hotel_positions,
  32. update_hotel_positions,
  33. query_hotel_positions,
  34. delete_hotel_positions,
  35. get_hotel_group_brands_list,
  36. add_hotel_group_brands,
  37. update_hotel_group_brands,
  38. query_hotel_group_brands,
  39. delete_hotel_group_brands
  40. )
  41. # 导入新的名片图片解析函数和添加名片函数
  42. from app.core.data_parse.parse_card import process_business_card_image, add_business_card, delete_business_card, batch_process_business_card_images
  43. # 导入网页文本解析函数
  44. from app.core.data_parse.parse_web import process_webpage_with_QWen, add_webpage_talent, batch_process_md
  45. # 导入简历解析函数
  46. from app.core.data_parse.parse_resume import batch_parse_resumes
  47. # 导入门墩儿数据处理函数
  48. from app.core.data_parse.parse_menduner import batch_process_menduner_data
  49. # 导入图片批量处理函数
  50. from app.core.data_parse.parse_pic import batch_process_images
  51. from app.config.config import DevelopmentConfig, ProductionConfig
  52. import logging
  53. import boto3
  54. from botocore.config import Config
  55. from botocore.exceptions import ClientError
  56. from io import BytesIO
  57. import base64
  58. import os
  59. import urllib.parse
  60. from minio import Minio
  61. # Define logger
  62. logger = logging.getLogger(__name__)
  63. # For failure responses
  64. def failed(message, code=500):
  65. return {
  66. 'success': False,
  67. 'message': message,
  68. 'data': None
  69. }, code
  70. # 根据环境选择配置
  71. if os.environ.get('FLASK_ENV') == 'production':
  72. config = ProductionConfig()
  73. else:
  74. config = DevelopmentConfig()
  75. # 使用配置变量
  76. minio_url = f"{'https' if config.MINIO_SECURE else 'http'}://{config.MINIO_HOST}"
  77. minio_access_key = config.MINIO_USER
  78. minio_secret_key = config.MINIO_PASSWORD
  79. minio_bucket = config.MINIO_BUCKET
  80. use_ssl = config.MINIO_SECURE
  81. def get_minio_client():
  82. """获取 MinIO 客户端实例"""
  83. return Minio(
  84. '192.168.3.143:9000',
  85. access_key=config.MINIO_USER,
  86. secret_key=config.MINIO_PASSWORD,
  87. secure=config.MINIO_SECURE
  88. )
  89. # 名片解析接口
  90. @bp.route('/business-card-parse', methods=['POST'])
  91. def parse_business_card_route():
  92. """
  93. 解析名片图片并提取信息的API接口(仅解析,不保存到数据库)
  94. 请求参数:
  95. - image: 名片图片文件 (multipart/form-data)
  96. 返回:
  97. - JSON: 包含提取的名片信息和处理状态
  98. 注意:此接口仅负责图片解析和信息提取,不会将数据保存到数据库
  99. """
  100. # 检查是否上传了文件
  101. if 'image' not in request.files:
  102. return jsonify({
  103. 'success': False,
  104. 'message': '未上传图片',
  105. 'data': None
  106. }), 400
  107. image_file = request.files['image']
  108. # 检查文件是否为空
  109. if image_file.filename == '':
  110. return jsonify({
  111. 'success': False,
  112. 'message': '未选择文件',
  113. 'data': None
  114. }), 400
  115. # 检查文件类型是否为图片
  116. if not image_file.content_type.startswith('image/'):
  117. return jsonify({
  118. 'success': False,
  119. 'message': '上传的文件不是图片',
  120. 'data': None
  121. }), 400
  122. # 处理名片图片
  123. result = process_business_card_image(image_file)
  124. if result['success']:
  125. return jsonify(result), 200
  126. else:
  127. return jsonify(result), 500
  128. # 添加名片记录接口
  129. @bp.route('/add-business-card', methods=['POST'])
  130. def add_business_card_route():
  131. """
  132. 添加名片记录的API接口(解析图片并保存到数据库)
  133. 请求参数:
  134. - card_data: 名片信息数据 (JSON格式,可以通过form-data或JSON body传递)
  135. - image: 名片图片文件 (multipart/form-data,可选)
  136. 返回:
  137. - JSON: 包含保存结果和处理状态
  138. 注意:此接口负责业务逻辑处理,包括重复检查、MinIO上传和数据库保存
  139. """
  140. try:
  141. # 获取名片数据 - 支持两种方式
  142. card_data = None
  143. # 方式1:通过JSON body传递
  144. if request.is_json:
  145. card_data = request.get_json()
  146. # 方式2:通过form-data传递card_data字段
  147. elif 'card_data' in request.form:
  148. import json
  149. try:
  150. card_data = json.loads(request.form['card_data'])
  151. except json.JSONDecodeError:
  152. return jsonify({
  153. 'success': False,
  154. 'message': 'card_data格式错误,必须是有效的JSON字符串',
  155. 'data': None
  156. }), 400
  157. # 检查是否提供了名片数据
  158. if not card_data:
  159. return jsonify({
  160. 'success': False,
  161. 'message': '未提供名片数据,请通过JSON body或form-data的card_data字段传递',
  162. 'data': None
  163. }), 400
  164. # 获取可选的图片文件
  165. image_file = None
  166. if 'image' in request.files:
  167. image_file = request.files['image']
  168. # 检查文件是否为空
  169. if image_file.filename == '':
  170. image_file = None
  171. # 检查文件类型是否为图片
  172. elif not image_file.content_type.startswith('image/'):
  173. return jsonify({
  174. 'success': False,
  175. 'message': '上传的文件不是图片',
  176. 'data': None
  177. }), 400
  178. # 调用业务逻辑函数处理名片数据
  179. result = add_business_card(card_data, image_file)
  180. # 根据处理结果设置HTTP状态码
  181. if result['success']:
  182. if result['code'] == 200:
  183. status_code = 200
  184. elif result['code'] == 202:
  185. status_code = 202 # Accepted - 创建成功但有疑似重复记录
  186. else:
  187. status_code = 200
  188. else:
  189. if result['code'] == 400:
  190. status_code = 400
  191. else:
  192. status_code = 500
  193. return jsonify(result), status_code
  194. except Exception as e:
  195. logger.error(f"添加名片记录失败: {str(e)}")
  196. return jsonify({
  197. 'success': False,
  198. 'message': f'添加名片记录失败: {str(e)}',
  199. 'data': None
  200. }), 500
  201. # 更新名片信息接口
  202. @bp.route('/business-cards/<int:card_id>', methods=['PUT'])
  203. def update_business_card_route(card_id):
  204. """
  205. 更新名片信息的API接口
  206. 路径参数:
  207. - card_id: 名片记录ID
  208. 请求参数:
  209. - JSON格式的名片信息
  210. 返回:
  211. - JSON: 包含更新后的名片信息和处理状态
  212. """
  213. # 获取请求数据
  214. data = request.json
  215. if not data:
  216. return jsonify({
  217. 'success': False,
  218. 'message': '请求数据为空',
  219. 'data': None
  220. }), 400
  221. # 调用业务逻辑函数处理更新
  222. result = update_business_card(card_id, data)
  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. # 获取所有名片记录的API接口
  229. @bp.route('/get-business-cards', methods=['GET'])
  230. def get_business_cards_route():
  231. """
  232. 获取所有名片记录的API接口
  233. 返回:
  234. - JSON: 包含名片记录列表和处理状态
  235. """
  236. # 调用业务逻辑函数获取名片列表
  237. result = get_business_cards()
  238. # 根据处理结果设置HTTP状态码
  239. status_code = 200 if result['success'] else 500
  240. return jsonify(result), status_code
  241. @bp.route('/update-business-cards/<int:card_id>/status', methods=['PUT'])
  242. def update_business_card_status_route(card_id):
  243. """
  244. 更新名片状态的API接口
  245. 路径参数:
  246. - card_id: 名片记录ID
  247. 请求参数:
  248. - JSON格式,包含status字段
  249. 返回:
  250. - JSON: 包含更新后的名片信息和处理状态
  251. """
  252. # 获取请求数据
  253. data = request.json
  254. if not data or 'status' not in data:
  255. return jsonify({
  256. 'success': False,
  257. 'message': '请求数据为空或缺少status字段',
  258. 'data': None
  259. }), 400
  260. status = data['status']
  261. # 调用业务逻辑函数处理状态更新
  262. result = update_business_card_status(card_id, status)
  263. # 根据处理结果设置HTTP状态码
  264. status_code = 200 if result['success'] else 500
  265. if 'not found' in result.get('message', '').lower() or '未找到' in result.get('message', ''):
  266. status_code = 404
  267. return jsonify(result), status_code
  268. # 从MinIO获取名片图片的API接口
  269. @bp.route('/business-cards/image/<path:image_path>', methods=['GET'])
  270. def get_business_card_image(image_path):
  271. """
  272. 从MinIO获取名片图片的API接口
  273. 路径参数:
  274. - image_path: MinIO中的图片路径
  275. 返回:
  276. - 图片数据流
  277. """
  278. try:
  279. # 记录下载请求信息,便于调试
  280. logger.info(f"获取名片图片请求: {image_path}")
  281. # 获取 MinIO 客户端
  282. minio_client = get_minio_client()
  283. if not minio_client:
  284. return jsonify(failed("MinIO客户端初始化失败")), 500
  285. try:
  286. # 使用正确的MinIO客户端方法
  287. data = minio_client.get_object(minio_bucket, image_path)
  288. # 创建内存文件流
  289. file_stream = BytesIO(data.read())
  290. # 获取文件名
  291. file_name = image_path.split('/')[-1]
  292. # 返回文件
  293. return send_file(
  294. file_stream,
  295. as_attachment=False, # 设置为False,让浏览器直接显示图片
  296. download_name=file_name,
  297. mimetype='image/jpeg' # 根据实际图片类型设置
  298. )
  299. except Exception as e:
  300. logger.error(f"MinIO获取文件失败: {str(e)}")
  301. return jsonify(failed(f"文件获取失败: {str(e)}")), 404
  302. except Exception as e:
  303. logger.error(f"文件下载失败: {str(e)}")
  304. return jsonify(failed(str(e))), 500
  305. finally:
  306. # 确保关闭数据流
  307. if 'data' in locals():
  308. data.close()
  309. # 创建人才标签接口
  310. @bp.route('/create-talent-tag', methods=['POST'])
  311. def create_talent_tag_route():
  312. """
  313. 创建人才标签的API接口
  314. 请求参数:
  315. - JSON格式,包含以下字段:
  316. - name: 标签名称
  317. - category: 标签分类
  318. - description: 标签描述
  319. - status: 启用状态,默认为'active'
  320. 返回:
  321. - JSON: 包含创建结果和标签信息
  322. """
  323. try:
  324. # 获取请求数据
  325. data = request.get_json()
  326. if not data:
  327. return jsonify({
  328. 'success': False,
  329. 'message': '请求数据为空',
  330. 'data': None
  331. }), 400
  332. # 验证必要字段
  333. if 'name' not in data or not data['name']:
  334. return jsonify({
  335. 'success': False,
  336. 'message': '标签名称不能为空',
  337. 'data': None
  338. }), 400
  339. # 处理分类字段,如果未提供则设置默认值
  340. if 'category' not in data or not data['category']:
  341. data['category'] = '未分类'
  342. # 调用业务逻辑函数处理创建
  343. result = create_talent_tag(data)
  344. # 根据处理结果设置HTTP状态码
  345. status_code = 200 if result['success'] else 500
  346. return jsonify(result), status_code
  347. except Exception as e:
  348. logger.error(f"创建人才标签失败: {str(e)}")
  349. return jsonify({
  350. 'success': False,
  351. 'message': f'创建人才标签失败: {str(e)}',
  352. 'data': None
  353. }), 500
  354. # 获取人才标签列表接口
  355. @bp.route('/get-talent-tag-list', methods=['GET'])
  356. def get_talent_tag_list_route():
  357. """
  358. 获取人才标签列表的API接口
  359. 返回:
  360. - JSON: 包含人才标签列表和处理状态
  361. """
  362. try:
  363. # 调用业务逻辑函数获取人才标签列表
  364. result = get_talent_tag_list()
  365. # 根据处理结果设置HTTP状态码
  366. status_code = 200 if result['success'] else 500
  367. return jsonify(result), status_code
  368. except Exception as e:
  369. logger.error(f"获取人才标签列表失败: {str(e)}")
  370. return jsonify({
  371. 'success': False,
  372. 'message': f'获取人才标签列表失败: {str(e)}',
  373. 'data': []
  374. }), 500
  375. # 更新人才标签接口
  376. @bp.route('/update-talent-tag/<int:tag_id>', methods=['PUT'])
  377. def update_talent_tag_route(tag_id):
  378. """
  379. 更新人才标签的API接口
  380. 路径参数:
  381. - tag_id: 标签节点ID
  382. 请求参数:
  383. - JSON格式,可能包含以下字段:
  384. - name: 标签名称
  385. - category: 标签分类
  386. - description: 标签描述
  387. - status: 启用状态
  388. 返回:
  389. - JSON: 包含更新结果和标签信息
  390. """
  391. try:
  392. # 获取请求数据
  393. data = request.get_json()
  394. if not data:
  395. return jsonify({
  396. 'success': False,
  397. 'message': '请求数据为空',
  398. 'data': None
  399. }), 400
  400. # 调用业务逻辑函数处理更新
  401. result = update_talent_tag(tag_id, data)
  402. # 根据处理结果设置HTTP状态码
  403. if not result['success']:
  404. if result['code'] == 404:
  405. status_code = 404
  406. elif result['code'] == 400:
  407. status_code = 400
  408. else:
  409. status_code = 500
  410. else:
  411. status_code = 200
  412. return jsonify(result), status_code
  413. except Exception as e:
  414. logger.error(f"更新人才标签失败: {str(e)}")
  415. return jsonify({
  416. 'success': False,
  417. 'message': f'更新人才标签失败: {str(e)}',
  418. 'data': None
  419. }), 500
  420. # 删除人才标签接口
  421. @bp.route('/delete-talent-tag/<int:tag_id>', methods=['DELETE'])
  422. def delete_talent_tag_route(tag_id):
  423. """
  424. 删除人才标签的API接口
  425. 路径参数:
  426. - tag_id: 标签节点ID
  427. 返回:
  428. - JSON: 包含删除结果和被删除的标签信息
  429. """
  430. try:
  431. # 调用业务逻辑函数执行删除
  432. result = delete_talent_tag(tag_id)
  433. # 根据处理结果设置HTTP状态码
  434. if not result['success']:
  435. if result['code'] == 404:
  436. status_code = 404
  437. else:
  438. status_code = 500
  439. else:
  440. status_code = 200
  441. return jsonify(result), status_code
  442. except Exception as e:
  443. logger.error(f"删除人才标签失败: {str(e)}")
  444. return jsonify({
  445. 'success': False,
  446. 'message': f'删除人才标签失败: {str(e)}',
  447. 'data': None
  448. }), 500
  449. @bp.route('/query-kg', methods=['POST'])
  450. def query_kg():
  451. """
  452. 查询知识图谱API接口
  453. 请求参数:
  454. - query_requirement: 查询需求描述(JSON格式)
  455. 返回:
  456. - JSON: 包含查询结果和处理状态
  457. """
  458. try:
  459. # 获取请求数据
  460. data = request.json
  461. if not data or 'query_requirement' not in data:
  462. return jsonify({
  463. 'code': 400,
  464. 'success': False,
  465. 'message': '请求数据为空或缺少query_requirement字段',
  466. 'data': []
  467. }), 400
  468. query_requirement = data['query_requirement']
  469. # 调用业务逻辑函数执行查询
  470. result = query_neo4j_graph(query_requirement)
  471. # 根据处理结果设置HTTP状态码
  472. status_code = 200 if result['success'] else 500
  473. return jsonify(result), status_code
  474. except Exception as e:
  475. logger.error(f"查询知识图谱失败: {str(e)}")
  476. return jsonify({
  477. 'code': 500,
  478. 'success': False,
  479. 'message': f"查询知识图谱失败: {str(e)}",
  480. 'data': []
  481. }), 500
  482. @bp.route('/talent-get-tags/<int:talent_id>', methods=['GET'])
  483. def talent_get_tags_route(talent_id):
  484. """
  485. 获取人才标签的API接口
  486. 路径参数:
  487. - talent_id: 人才节点ID
  488. 返回:
  489. - JSON: 包含人才关联的标签列表和处理状态
  490. """
  491. try:
  492. # 调用业务逻辑函数获取人才标签
  493. result = talent_get_tags(talent_id)
  494. # 根据处理结果设置HTTP状态码
  495. status_code = 200 if result['success'] else 500
  496. return jsonify(result), status_code
  497. except Exception as e:
  498. logger.error(f"获取人才标签失败: {str(e)}")
  499. return jsonify({
  500. 'code': 500,
  501. 'success': False,
  502. 'message': f"获取人才标签失败: {str(e)}",
  503. 'data': []
  504. }), 500
  505. @bp.route('/talent-update-tags', methods=['POST'])
  506. def talent_update_tags_route():
  507. """
  508. 更新人才标签关系的API接口
  509. 请求参数:
  510. - JSON数组,包含talent和tag字段的对象列表
  511. 例如: [
  512. {"talent": 12345, "tag": "市场营销"},
  513. {"talent": 12345, "tag": "酒店管理"}
  514. ]
  515. 返回:
  516. - JSON: 包含更新结果的状态信息
  517. """
  518. try:
  519. # 获取请求数据
  520. data = request.json
  521. if not data:
  522. return jsonify({
  523. 'code': 400,
  524. 'success': False,
  525. 'message': '请求数据为空',
  526. 'data': None
  527. }), 400
  528. # 调用业务逻辑函数处理标签关系更新
  529. result = talent_update_tags(data)
  530. # 根据处理结果设置HTTP状态码
  531. if result['code'] == 200:
  532. status_code = 200
  533. elif result['code'] == 206:
  534. status_code = 206 # Partial Content
  535. elif result['code'] == 400:
  536. status_code = 400 # Bad Request
  537. elif result['code'] == 404:
  538. status_code = 404 # Not Found
  539. else:
  540. status_code = 500 # Internal Server Error
  541. return jsonify(result), status_code
  542. except Exception as e:
  543. logger.error(f"更新人才标签关系失败: {str(e)}")
  544. return jsonify({
  545. 'code': 500,
  546. 'success': False,
  547. 'message': f"更新人才标签关系失败: {str(e)}",
  548. 'data': None
  549. }), 500
  550. # 测试MinIO连接
  551. def test_minio_connection():
  552. """测试MinIO连接是否正常"""
  553. try:
  554. client = get_minio_client()
  555. if client.bucket_exists(minio_bucket):
  556. return {
  557. 'success': True,
  558. 'message': f'连接MinIO服务器成功,存储桶 {minio_bucket} 存在',
  559. 'config': {
  560. 'host': config.MINIO_HOST,
  561. 'bucket': minio_bucket,
  562. 'secure': use_ssl
  563. }
  564. }
  565. else:
  566. return {
  567. 'success': False,
  568. 'message': f'连接MinIO服务器成功,但存储桶 {minio_bucket} 不存在',
  569. 'config': {
  570. 'host': config.MINIO_HOST,
  571. 'bucket': minio_bucket,
  572. 'secure': use_ssl
  573. }
  574. }
  575. except Exception as e:
  576. return {
  577. 'success': False,
  578. 'message': f'连接MinIO服务器失败: {str(e)}',
  579. 'config': {
  580. 'host': config.MINIO_HOST,
  581. 'bucket': minio_bucket,
  582. 'secure': use_ssl
  583. }
  584. }
  585. # MinIO测试接口
  586. @bp.route('/test-minio-connection', methods=['GET'])
  587. def test_minio_connection_route():
  588. """
  589. 测试MinIO连接的API接口
  590. 返回:
  591. - JSON: 包含连接测试结果
  592. """
  593. try:
  594. result = test_minio_connection()
  595. status_code = 200 if result['success'] else 500
  596. return jsonify(result), status_code
  597. except Exception as e:
  598. logger.error(f"测试MinIO连接失败: {str(e)}")
  599. return jsonify({
  600. 'success': False,
  601. 'message': f'测试MinIO连接失败: {str(e)}',
  602. 'config': {
  603. 'host': config.MINIO_HOST,
  604. 'bucket': minio_bucket,
  605. 'secure': use_ssl
  606. }
  607. }), 500
  608. # 获取单个名片记录的API接口
  609. @bp.route('/get-business-card/<int:card_id>', methods=['GET'])
  610. def get_business_card_route(card_id):
  611. """
  612. 获取单个名片记录的API接口
  613. 路径参数:
  614. - card_id: 名片记录ID
  615. 返回:
  616. - JSON: 包含名片记录信息和处理状态
  617. """
  618. # 调用业务逻辑函数获取名片记录
  619. result = get_business_card(card_id)
  620. # 根据处理结果设置HTTP状态码
  621. if not result['success']:
  622. if result['code'] == 404:
  623. status_code = 404
  624. else:
  625. status_code = 500
  626. else:
  627. status_code = 200
  628. return jsonify(result), status_code
  629. @bp.route('/search-business-cards-by-mobile', methods=['GET'])
  630. def search_business_cards_by_mobile_route():
  631. """
  632. 根据手机号码搜索名片记录的API接口
  633. 查询参数:
  634. - mobile: 要搜索的手机号码
  635. 返回:
  636. - JSON: 包含搜索到的名片记录列表和处理状态
  637. 示例:
  638. GET /search-business-cards-by-mobile?mobile=13800138000
  639. """
  640. try:
  641. # 获取查询参数
  642. mobile_number = request.args.get('mobile', '').strip()
  643. if not mobile_number:
  644. return jsonify({
  645. 'success': False,
  646. 'message': '请提供要搜索的手机号码',
  647. 'data': []
  648. }), 400
  649. # 调用业务逻辑函数搜索名片记录
  650. result = search_business_cards_by_mobile(mobile_number)
  651. # 根据处理结果设置HTTP状态码
  652. if result['code'] == 200:
  653. status_code = 200
  654. elif result['code'] == 400:
  655. status_code = 400
  656. else:
  657. status_code = 500
  658. return jsonify(result), status_code
  659. except Exception as e:
  660. # 处理未预期的异常
  661. error_msg = f"根据手机号码搜索名片时发生错误: {str(e)}"
  662. logger.error(error_msg, exc_info=True)
  663. return jsonify({
  664. 'success': False,
  665. 'message': error_msg,
  666. 'data': []
  667. }), 500
  668. @bp.route('/get-hotel-positions-list', methods=['GET'])
  669. def get_hotel_positions_list_route():
  670. """
  671. 获取酒店职位数据表全部记录的API接口
  672. 返回:
  673. - JSON: 包含酒店职位记录列表和处理状态
  674. """
  675. try:
  676. # 调用业务逻辑函数获取酒店职位列表
  677. result = get_hotel_positions_list()
  678. # 根据处理结果设置HTTP状态码
  679. status_code = 200 if result['success'] else 500
  680. return jsonify(result), status_code
  681. except Exception as e:
  682. # 处理未预期的异常
  683. error_msg = f"获取酒店职位列表时发生错误: {str(e)}"
  684. logger.error(error_msg, exc_info=True)
  685. return jsonify({
  686. 'success': False,
  687. 'message': error_msg,
  688. 'data': [],
  689. 'count': 0
  690. }), 500
  691. @bp.route('/add-hotel-positions', methods=['POST'])
  692. def add_hotel_positions_route():
  693. """
  694. 新增酒店职位数据表记录的API接口
  695. 请求参数:
  696. - JSON格式,包含以下字段:
  697. - department_zh: 部门中文名称 (必填)
  698. - department_en: 部门英文名称 (必填)
  699. - position_zh: 职位中文名称 (必填)
  700. - position_en: 职位英文名称 (必填)
  701. - position_abbr: 职位英文缩写 (可选)
  702. - level_zh: 职级中文名称 (必填)
  703. - level_en: 职级英文名称 (必填)
  704. - created_by: 创建者 (可选)
  705. - updated_by: 更新者 (可选)
  706. - status: 状态 (可选)
  707. 返回:
  708. - JSON: 包含创建结果和职位信息
  709. """
  710. try:
  711. # 获取请求数据
  712. data = request.get_json()
  713. if not data:
  714. return jsonify({
  715. 'success': False,
  716. 'message': '请求数据为空',
  717. 'data': None
  718. }), 400
  719. # 调用业务逻辑函数处理创建
  720. result = add_hotel_positions(data)
  721. # 根据处理结果设置HTTP状态码
  722. if result['code'] == 200:
  723. status_code = 201 # Created
  724. elif result['code'] == 400:
  725. status_code = 400 # Bad Request
  726. elif result['code'] == 409:
  727. status_code = 409 # Conflict
  728. else:
  729. status_code = 500 # Internal Server Error
  730. return jsonify(result), status_code
  731. except Exception as e:
  732. # 处理未预期的异常
  733. error_msg = f"创建酒店职位记录时发生错误: {str(e)}"
  734. logger.error(error_msg, exc_info=True)
  735. return jsonify({
  736. 'success': False,
  737. 'message': error_msg,
  738. 'data': None
  739. }), 500
  740. @bp.route('/update-hotel-positions/<int:position_id>', methods=['PUT'])
  741. def update_hotel_positions_route(position_id):
  742. """
  743. 修改酒店职位数据表记录的API接口
  744. 路径参数:
  745. - position_id: 职位记录ID
  746. 请求参数:
  747. - JSON格式,可能包含以下字段:
  748. - department_zh: 部门中文名称
  749. - department_en: 部门英文名称
  750. - position_zh: 职位中文名称
  751. - position_en: 职位英文名称
  752. - position_abbr: 职位英文缩写
  753. - level_zh: 职级中文名称
  754. - level_en: 职级英文名称
  755. - updated_by: 更新者
  756. - status: 状态
  757. 返回:
  758. - JSON: 包含更新结果和职位信息
  759. """
  760. try:
  761. # 获取请求数据
  762. data = request.get_json()
  763. if not data:
  764. return jsonify({
  765. 'success': False,
  766. 'message': '请求数据为空',
  767. 'data': None
  768. }), 400
  769. # 调用业务逻辑函数处理更新
  770. result = update_hotel_positions(position_id, data)
  771. # 根据处理结果设置HTTP状态码
  772. if result['code'] == 200:
  773. status_code = 200 # OK
  774. elif result['code'] == 400:
  775. status_code = 400 # Bad Request
  776. elif result['code'] == 404:
  777. status_code = 404 # Not Found
  778. elif result['code'] == 409:
  779. status_code = 409 # Conflict
  780. else:
  781. status_code = 500 # Internal Server Error
  782. return jsonify(result), status_code
  783. except Exception as e:
  784. # 处理未预期的异常
  785. error_msg = f"更新酒店职位记录时发生错误: {str(e)}"
  786. logger.error(error_msg, exc_info=True)
  787. return jsonify({
  788. 'success': False,
  789. 'message': error_msg,
  790. 'data': None
  791. }), 500
  792. @bp.route('/query-hotel-positions/<int:position_id>', methods=['GET'])
  793. def query_hotel_positions_route(position_id):
  794. """
  795. 查找指定ID的酒店职位数据表记录的API接口
  796. 路径参数:
  797. - position_id: 职位记录ID
  798. 返回:
  799. - JSON: 包含查找结果和职位信息
  800. """
  801. try:
  802. # 调用业务逻辑函数查找职位记录
  803. result = query_hotel_positions(position_id)
  804. # 根据处理结果设置HTTP状态码
  805. if result['code'] == 200:
  806. status_code = 200 # OK
  807. elif result['code'] == 404:
  808. status_code = 404 # Not Found
  809. else:
  810. status_code = 500 # Internal Server Error
  811. return jsonify(result), status_code
  812. except Exception as e:
  813. # 处理未预期的异常
  814. error_msg = f"查找酒店职位记录时发生错误: {str(e)}"
  815. logger.error(error_msg, exc_info=True)
  816. return jsonify({
  817. 'success': False,
  818. 'message': error_msg,
  819. 'data': None
  820. }), 500
  821. @bp.route('/delete-hotel-positions/<int:position_id>', methods=['DELETE'])
  822. def delete_hotel_positions_route(position_id):
  823. """
  824. 删除指定ID的酒店职位数据表记录的API接口
  825. 路径参数:
  826. - position_id: 职位记录ID
  827. 返回:
  828. - JSON: 包含删除结果和被删除的职位信息
  829. """
  830. try:
  831. # 调用业务逻辑函数删除职位记录
  832. result = delete_hotel_positions(position_id)
  833. # 根据处理结果设置HTTP状态码
  834. if result['code'] == 200:
  835. status_code = 200 # OK
  836. elif result['code'] == 404:
  837. status_code = 404 # Not Found
  838. else:
  839. status_code = 500 # Internal Server Error
  840. return jsonify(result), status_code
  841. except Exception as e:
  842. # 处理未预期的异常
  843. error_msg = f"删除酒店职位记录时发生错误: {str(e)}"
  844. logger.error(error_msg, exc_info=True)
  845. return jsonify({
  846. 'success': False,
  847. 'message': error_msg,
  848. 'data': None
  849. }), 500
  850. @bp.route('/get-hotel-group-brands-list', methods=['GET'])
  851. def get_hotel_group_brands_list_route():
  852. """
  853. 获取酒店集团子品牌数据表全部记录的API接口
  854. 返回:
  855. - JSON: 包含酒店集团品牌记录列表和处理状态
  856. """
  857. try:
  858. # 调用业务逻辑函数获取酒店集团品牌列表
  859. result = get_hotel_group_brands_list()
  860. # 根据处理结果设置HTTP状态码
  861. status_code = 200 if result['success'] else 500
  862. return jsonify(result), status_code
  863. except Exception as e:
  864. # 处理未预期的异常
  865. error_msg = f"获取酒店集团品牌列表时发生错误: {str(e)}"
  866. logger.error(error_msg, exc_info=True)
  867. return jsonify({
  868. 'success': False,
  869. 'message': error_msg,
  870. 'data': [],
  871. 'count': 0
  872. }), 500
  873. @bp.route('/add-hotel-group-brands', methods=['POST'])
  874. def add_hotel_group_brands_route():
  875. """
  876. 新增酒店集团子品牌数据表记录的API接口
  877. 请求参数:
  878. - JSON格式,包含以下字段:
  879. - group_name_en: 集团英文名称 (必填)
  880. - group_name_zh: 集团中文名称 (必填)
  881. - brand_name_en: 品牌英文名称 (必填)
  882. - brand_name_zh: 品牌中文名称 (必填)
  883. - positioning_level_en: 定位级别英文名称 (必填)
  884. - positioning_level_zh: 定位级别中文名称 (必填)
  885. - created_by: 创建者 (可选)
  886. - updated_by: 更新者 (可选)
  887. - status: 状态 (可选)
  888. 返回:
  889. - JSON: 包含创建结果和品牌信息
  890. """
  891. try:
  892. # 获取请求数据
  893. data = request.get_json()
  894. if not data:
  895. return jsonify({
  896. 'success': False,
  897. 'message': '请求数据为空',
  898. 'data': None
  899. }), 400
  900. # 调用业务逻辑函数处理创建
  901. result = add_hotel_group_brands(data)
  902. # 根据处理结果设置HTTP状态码
  903. if result['code'] == 200:
  904. status_code = 201 # Created
  905. elif result['code'] == 400:
  906. status_code = 400 # Bad Request
  907. elif result['code'] == 409:
  908. status_code = 409 # Conflict
  909. else:
  910. status_code = 500 # Internal Server Error
  911. return jsonify(result), status_code
  912. except Exception as e:
  913. # 处理未预期的异常
  914. error_msg = f"创建酒店集团品牌记录时发生错误: {str(e)}"
  915. logger.error(error_msg, exc_info=True)
  916. return jsonify({
  917. 'success': False,
  918. 'message': error_msg,
  919. 'data': None
  920. }), 500
  921. @bp.route('/update-hotel-group-brands/<int:brand_id>', methods=['PUT'])
  922. def update_hotel_group_brands_route(brand_id):
  923. """
  924. 修改酒店集团子品牌数据表记录的API接口
  925. 路径参数:
  926. - brand_id: 品牌记录ID
  927. 请求参数:
  928. - JSON格式,可能包含以下字段:
  929. - group_name_en: 集团英文名称
  930. - group_name_zh: 集团中文名称
  931. - brand_name_en: 品牌英文名称
  932. - brand_name_zh: 品牌中文名称
  933. - positioning_level_en: 定位级别英文名称
  934. - positioning_level_zh: 定位级别中文名称
  935. - updated_by: 更新者
  936. - status: 状态
  937. 返回:
  938. - JSON: 包含更新结果和品牌信息
  939. """
  940. try:
  941. # 获取请求数据
  942. data = request.get_json()
  943. if not data:
  944. return jsonify({
  945. 'success': False,
  946. 'message': '请求数据为空',
  947. 'data': None
  948. }), 400
  949. # 调用业务逻辑函数处理更新
  950. result = update_hotel_group_brands(brand_id, data)
  951. # 根据处理结果设置HTTP状态码
  952. if result['code'] == 200:
  953. status_code = 200 # OK
  954. elif result['code'] == 400:
  955. status_code = 400 # Bad Request
  956. elif result['code'] == 404:
  957. status_code = 404 # Not Found
  958. elif result['code'] == 409:
  959. status_code = 409 # Conflict
  960. else:
  961. status_code = 500 # Internal Server Error
  962. return jsonify(result), status_code
  963. except Exception as e:
  964. # 处理未预期的异常
  965. error_msg = f"更新酒店集团品牌记录时发生错误: {str(e)}"
  966. logger.error(error_msg, exc_info=True)
  967. return jsonify({
  968. 'success': False,
  969. 'message': error_msg,
  970. 'data': None
  971. }), 500
  972. @bp.route('/query-hotel-group-brands/<int:brand_id>', methods=['GET'])
  973. def query_hotel_group_brands_route(brand_id):
  974. """
  975. 查找指定ID的酒店集团子品牌数据表记录的API接口
  976. 路径参数:
  977. - brand_id: 品牌记录ID
  978. 返回:
  979. - JSON: 包含查找结果和品牌信息
  980. """
  981. try:
  982. # 调用业务逻辑函数查找品牌记录
  983. result = query_hotel_group_brands(brand_id)
  984. # 根据处理结果设置HTTP状态码
  985. if result['code'] == 200:
  986. status_code = 200 # OK
  987. elif result['code'] == 404:
  988. status_code = 404 # Not Found
  989. else:
  990. status_code = 500 # Internal Server Error
  991. return jsonify(result), status_code
  992. except Exception as e:
  993. # 处理未预期的异常
  994. error_msg = f"查找酒店集团品牌记录时发生错误: {str(e)}"
  995. logger.error(error_msg, exc_info=True)
  996. return jsonify({
  997. 'success': False,
  998. 'message': error_msg,
  999. 'data': None
  1000. }), 500
  1001. @bp.route('/delete-hotel-group-brands/<int:brand_id>', methods=['DELETE'])
  1002. def delete_hotel_group_brands_route(brand_id):
  1003. """
  1004. 删除指定ID的酒店集团子品牌数据表记录的API接口
  1005. 路径参数:
  1006. - brand_id: 品牌记录ID
  1007. 返回:
  1008. - JSON: 包含删除结果和被删除的品牌信息
  1009. """
  1010. try:
  1011. # 调用业务逻辑函数删除品牌记录
  1012. result = delete_hotel_group_brands(brand_id)
  1013. # 根据处理结果设置HTTP状态码
  1014. if result['code'] == 200:
  1015. status_code = 200 # OK
  1016. elif result['code'] == 404:
  1017. status_code = 404 # Not Found
  1018. else:
  1019. status_code = 500 # Internal Server Error
  1020. return jsonify(result), status_code
  1021. except Exception as e:
  1022. # 处理未预期的异常
  1023. error_msg = f"删除酒店集团品牌记录时发生错误: {str(e)}"
  1024. logger.error(error_msg, exc_info=True)
  1025. return jsonify({
  1026. 'success': False,
  1027. 'message': error_msg,
  1028. 'data': None
  1029. }), 500
  1030. # ==================================
  1031. # 重复记录处理API接口
  1032. # ==================================
  1033. @bp.route('/get-duplicate-records', methods=['GET'])
  1034. def get_duplicate_records_route():
  1035. """
  1036. 获取重复记录列表的API接口
  1037. 查询参数:
  1038. - status: 可选,筛选特定状态的记录 ('pending', 'processed', 'ignored')
  1039. 返回:
  1040. - JSON: 包含重复记录列表和处理状态
  1041. """
  1042. try:
  1043. # 获取查询参数
  1044. status = request.args.get('status', None)
  1045. # 验证status参数的有效性
  1046. if status and status not in ['pending', 'processed', 'ignored']:
  1047. return jsonify({
  1048. 'success': False,
  1049. 'message': 'status参数无效,必须为 pending、processed 或 ignored',
  1050. 'data': None
  1051. }), 400
  1052. # 调用业务逻辑函数获取重复记录列表
  1053. result = get_duplicate_records(status)
  1054. # 根据处理结果设置HTTP状态码
  1055. status_code = 200 if result['success'] else 500
  1056. return jsonify(result), status_code
  1057. except Exception as e:
  1058. # 处理未预期的异常
  1059. error_msg = f"获取重复记录列表时发生错误: {str(e)}"
  1060. logger.error(error_msg, exc_info=True)
  1061. return jsonify({
  1062. 'success': False,
  1063. 'message': error_msg,
  1064. 'data': [],
  1065. 'count': 0
  1066. }), 500
  1067. @bp.route('/process-duplicate-record/<int:duplicate_id>', methods=['POST'])
  1068. def process_duplicate_record_route(duplicate_id):
  1069. """
  1070. 处理重复记录的API接口
  1071. 路径参数:
  1072. - duplicate_id: 重复记录ID
  1073. 请求参数:
  1074. - JSON格式,包含以下字段:
  1075. - action: 处理动作 (必填) ('merge_to_suspected', 'keep_main', 'ignore')
  1076. - selected_duplicate_id: 当action为'merge_to_suspected'时,选择的疑似重复记录ID (可选)
  1077. - processed_by: 处理人 (可选)
  1078. - notes: 处理备注 (可选)
  1079. 返回:
  1080. - JSON: 包含处理结果和状态信息
  1081. """
  1082. try:
  1083. # 获取请求数据
  1084. data = request.get_json()
  1085. if not data:
  1086. return jsonify({
  1087. 'success': False,
  1088. 'message': '请求数据为空',
  1089. 'data': None
  1090. }), 400
  1091. # 验证必填字段
  1092. action = data.get('action')
  1093. if not action:
  1094. return jsonify({
  1095. 'success': False,
  1096. 'message': '缺少必填字段: action',
  1097. 'data': None
  1098. }), 400
  1099. # 验证action参数的有效性
  1100. if action not in ['merge_to_suspected', 'keep_main', 'ignore']:
  1101. return jsonify({
  1102. 'success': False,
  1103. 'message': 'action参数无效,必须为 merge_to_suspected、keep_main 或 ignore',
  1104. 'data': None
  1105. }), 400
  1106. # 提取其他参数
  1107. selected_duplicate_id = data.get('selected_duplicate_id')
  1108. processed_by = data.get('processed_by')
  1109. notes = data.get('notes')
  1110. # 特殊验证:如果action为merge_to_suspected,必须提供selected_duplicate_id
  1111. if action == 'merge_to_suspected' and not selected_duplicate_id:
  1112. return jsonify({
  1113. 'success': False,
  1114. 'message': '执行merge_to_suspected操作时必须提供selected_duplicate_id',
  1115. 'data': None
  1116. }), 400
  1117. # 调用业务逻辑函数处理重复记录
  1118. result = process_duplicate_record(
  1119. duplicate_id=duplicate_id,
  1120. action=action,
  1121. selected_duplicate_id=selected_duplicate_id,
  1122. processed_by=processed_by,
  1123. notes=notes
  1124. )
  1125. # 根据处理结果设置HTTP状态码
  1126. if result['code'] == 200:
  1127. status_code = 200 # OK
  1128. elif result['code'] == 400:
  1129. status_code = 400 # Bad Request
  1130. elif result['code'] == 404:
  1131. status_code = 404 # Not Found
  1132. else:
  1133. status_code = 500 # Internal Server Error
  1134. return jsonify(result), status_code
  1135. except Exception as e:
  1136. # 处理未预期的异常
  1137. error_msg = f"处理重复记录时发生错误: {str(e)}"
  1138. logger.error(error_msg, exc_info=True)
  1139. return jsonify({
  1140. 'success': False,
  1141. 'message': error_msg,
  1142. 'data': None
  1143. }), 500
  1144. @bp.route('/get-duplicate-record-detail/<int:duplicate_id>', methods=['GET'])
  1145. def get_duplicate_record_detail_route(duplicate_id):
  1146. """
  1147. 获取指定重复记录详细信息的API接口
  1148. 路径参数:
  1149. - duplicate_id: 重复记录ID
  1150. 返回:
  1151. - JSON: 包含重复记录详细信息
  1152. """
  1153. try:
  1154. # 调用业务逻辑函数获取重复记录详情
  1155. result = get_duplicate_record_detail(duplicate_id)
  1156. # 根据处理结果设置HTTP状态码
  1157. if result['code'] == 200:
  1158. status_code = 200 # OK
  1159. elif result['code'] == 404:
  1160. status_code = 404 # Not Found
  1161. else:
  1162. status_code = 500 # Internal Server Error
  1163. return jsonify(result), status_code
  1164. except Exception as e:
  1165. # 处理未预期的异常
  1166. error_msg = f"获取重复记录详情时发生错误: {str(e)}"
  1167. logger.error(error_msg, exc_info=True)
  1168. return jsonify({
  1169. 'success': False,
  1170. 'message': error_msg,
  1171. 'data': None
  1172. }), 500
  1173. # 删除名片记录接口
  1174. @bp.route('/delete-business-card/<int:card_id>', methods=['DELETE'])
  1175. def delete_business_card_route(card_id):
  1176. """
  1177. 删除名片记录的API接口
  1178. 路径参数:
  1179. - card_id: 名片记录ID (必填)
  1180. 功能说明:
  1181. - 删除PostgreSQL数据库中business_cards表的指定记录
  1182. - 删除PostgreSQL数据库中duplicate_business_cards表的相关记录
  1183. - 删除MinIO存储中的名片图片文件
  1184. - 删除Neo4j图数据库中talent节点及其关联关系
  1185. 返回:
  1186. - JSON: 包含删除操作的结果状态和被删除的记录信息
  1187. 状态码:
  1188. - 200: 完全成功删除所有相关数据
  1189. - 206: 部分成功 (PostgreSQL删除成功,但Neo4j删除失败)
  1190. - 404: 未找到指定ID的名片记录
  1191. - 500: 删除操作失败
  1192. """
  1193. try:
  1194. # 验证card_id参数
  1195. if not card_id or card_id <= 0:
  1196. return jsonify({
  1197. 'success': False,
  1198. 'message': '无效的名片记录ID',
  1199. 'data': None
  1200. }), 400
  1201. # 调用删除函数
  1202. result = delete_business_card(card_id)
  1203. # 根据处理结果设置HTTP状态码和返回响应
  1204. if result['success']:
  1205. if result['code'] == 200:
  1206. status_code = 200 # 完全成功
  1207. elif result['code'] == 206:
  1208. status_code = 206 # 部分成功
  1209. else:
  1210. status_code = 200 # 默认成功
  1211. else:
  1212. if result['code'] == 404:
  1213. status_code = 404 # 未找到记录
  1214. elif result['code'] == 400:
  1215. status_code = 400 # 参数错误
  1216. else:
  1217. status_code = 500 # 服务器错误
  1218. return jsonify(result), status_code
  1219. except Exception as e:
  1220. logger.error(f"删除名片记录失败: {str(e)}")
  1221. return jsonify({
  1222. 'success': False,
  1223. 'message': f'删除名片记录失败: {str(e)}',
  1224. 'data': None
  1225. }), 500
  1226. # 修复损坏的重复记录接口
  1227. @bp.route('/fix-broken-duplicate-records', methods=['POST'])
  1228. def fix_broken_duplicate_records_route():
  1229. """
  1230. 修复duplicate_business_cards表中main_card_id为null的损坏记录
  1231. 功能说明:
  1232. - 查找所有main_card_id为null的损坏记录
  1233. - 删除这些损坏的记录以维护数据完整性
  1234. - 返回修复操作的详细结果
  1235. 返回:
  1236. - JSON: 包含修复操作的结果和被删除记录的信息
  1237. 状态码:
  1238. - 200: 修复成功
  1239. - 500: 修复失败
  1240. 注意:
  1241. - 此操作会永久删除损坏的记录
  1242. - 建议在系统维护时执行此操作
  1243. """
  1244. try:
  1245. # 调用修复函数
  1246. result = fix_broken_duplicate_records()
  1247. # 根据结果设置状态码
  1248. if result['success']:
  1249. status_code = 200
  1250. else:
  1251. status_code = 500
  1252. return jsonify(result), status_code
  1253. except Exception as e:
  1254. logger.error(f"修复损坏记录接口调用失败: {str(e)}")
  1255. return jsonify({
  1256. 'success': False,
  1257. 'message': f'修复损坏记录接口调用失败: {str(e)}',
  1258. 'data': None
  1259. }), 500
  1260. # 网页文本解析接口
  1261. @bp.route('/webpage-parse', methods=['POST'])
  1262. def webpage_parse_route():
  1263. """
  1264. 解析网页 Markdown 文本并提取人员信息的API接口
  1265. 请求参数:
  1266. - markdown_text: 网页的 Markdown 格式文本内容 (JSON格式)
  1267. - publish_time: 发布时间,用于career_path中的date字段 (JSON格式)
  1268. 请求体示例:
  1269. {
  1270. "markdown_text": "# 张三\n\n职位:高级经理\n\n公司:XX酒店\n\n![照片](http://example.com/photo.jpg)",
  1271. "publish_time": "2025-01-15"
  1272. }
  1273. 返回:
  1274. - JSON: 包含提取的人员信息和处理状态
  1275. 功能说明:
  1276. - 接收 Markdown 格式的网页文本
  1277. - 进行必要的格式和内容验证
  1278. - 使用 Qwen VL Max 模型提取人员信息
  1279. - 支持提取照片链接 (pic_url)
  1280. - 返回标准化的人员信息数据
  1281. 状态码:
  1282. - 200: 解析成功
  1283. - 400: 请求参数错误
  1284. - 500: 解析失败
  1285. """
  1286. try:
  1287. # 检查请求是否为 JSON 格式
  1288. if not request.is_json:
  1289. return jsonify({
  1290. 'success': False,
  1291. 'message': '请求必须是 JSON 格式',
  1292. 'data': None
  1293. }), 400
  1294. # 获取请求数据
  1295. data = request.get_json()
  1296. # 检查是否提供了 markdown_text 参数
  1297. if 'markdown_text' not in data:
  1298. return jsonify({
  1299. 'success': False,
  1300. 'message': '缺少必填参数: markdown_text',
  1301. 'data': None
  1302. }), 400
  1303. # 检查是否提供了 publish_time 参数
  1304. if 'publish_time' not in data:
  1305. return jsonify({
  1306. 'success': False,
  1307. 'message': '缺少必填参数: publish_time',
  1308. 'data': None
  1309. }), 400
  1310. markdown_text = data['markdown_text']
  1311. publish_time = data['publish_time']
  1312. # 验证 markdown_text 是否为字符串
  1313. if not isinstance(markdown_text, str):
  1314. return jsonify({
  1315. 'success': False,
  1316. 'message': 'markdown_text 必须是字符串类型',
  1317. 'data': None
  1318. }), 400
  1319. # 验证 publish_time 是否为字符串
  1320. if not isinstance(publish_time, str):
  1321. return jsonify({
  1322. 'success': False,
  1323. 'message': 'publish_time 必须是字符串类型',
  1324. 'data': None
  1325. }), 400
  1326. # 验证文本内容不能为空
  1327. if not markdown_text.strip():
  1328. return jsonify({
  1329. 'success': False,
  1330. 'message': 'markdown_text 内容不能为空',
  1331. 'data': None
  1332. }), 400
  1333. # 验证文本长度(防止过长的文本)
  1334. if len(markdown_text) > 50000: # 限制最大50KB
  1335. return jsonify({
  1336. 'success': False,
  1337. 'message': 'markdown_text 内容过长,最大支持50KB',
  1338. 'data': None
  1339. }), 400
  1340. # 基本的 Markdown 格式验证(可选)
  1341. # 检查是否包含一些基本的文本内容
  1342. if len(markdown_text.strip()) < 10:
  1343. return jsonify({
  1344. 'success': False,
  1345. 'message': 'markdown_text 内容过短,无法进行有效解析',
  1346. 'data': None
  1347. }), 400
  1348. # 记录解析请求
  1349. logger.info(f"开始解析网页文本,内容长度: {len(markdown_text)} 字符")
  1350. # 调用网页文本解析函数
  1351. extracted_data = process_webpage_with_QWen(markdown_text, publish_time)
  1352. # 返回成功结果
  1353. return jsonify({
  1354. 'success': True,
  1355. 'message': '网页文本解析成功',
  1356. 'data': extracted_data
  1357. }), 200
  1358. except Exception as e:
  1359. # 记录错误日志
  1360. error_msg = f"网页文本解析失败: {str(e)}"
  1361. logger.error(error_msg, exc_info=True)
  1362. # 返回错误响应
  1363. return jsonify({
  1364. 'success': False,
  1365. 'message': error_msg,
  1366. 'data': None
  1367. }), 500
  1368. # 添加网页人才信息接口
  1369. @bp.route('/add-webpage-talent', methods=['POST'])
  1370. def add_webpage_talent_route():
  1371. """
  1372. 添加网页人才信息的API接口,包括保存网页内容和创建名片记录
  1373. 请求参数:
  1374. - talent_list: 人才信息列表,每个item包含业务卡片格式的数据 (JSON数组)
  1375. - web_md: 网页markdown文本内容 (JSON字符串)
  1376. 请求体示例:
  1377. {
  1378. "talent_list": [
  1379. {
  1380. "name_zh": "张三",
  1381. "name_en": "Zhang San",
  1382. "title_zh": "总经理",
  1383. "title_en": "General Manager",
  1384. "hotel_zh": "北京万豪酒店",
  1385. "hotel_en": "Beijing Marriott Hotel",
  1386. "brand_group": "万豪",
  1387. "mobile": "13800138000",
  1388. "email": "zhangsan@example.com"
  1389. },
  1390. {
  1391. "name_zh": "李四",
  1392. "name_en": "Li Si",
  1393. "title_zh": "市场总监",
  1394. "title_en": "Marketing Director",
  1395. "hotel_zh": "上海希尔顿酒店",
  1396. "hotel_en": "Shanghai Hilton Hotel",
  1397. "brand_group": "希尔顿",
  1398. "mobile": "13900139000",
  1399. "email": "lisi@example.com"
  1400. }
  1401. ],
  1402. "web_md": "# 人事任命公告\n\n**1** 张三被任命为北京万豪酒店总经理...\n\n**2** 李四被任命为上海希尔顿酒店市场总监..."
  1403. }
  1404. 返回:
  1405. - JSON: 包含处理结果,包括成功和失败的记录统计
  1406. 功能说明:
  1407. - 将网页markdown内容保存到MinIO
  1408. - 循环处理talent_list中的每个人才记录
  1409. - 为每个人才创建business_card记录
  1410. - 使用与名片上传相同的重复检查逻辑
  1411. - 在business_card记录的updated_by字段中记录MinIO路径
  1412. 状态码:
  1413. - 200: 所有记录处理成功
  1414. - 206: 部分记录处理成功
  1415. - 400: 请求参数错误
  1416. - 500: 处理失败
  1417. """
  1418. try:
  1419. # 检查请求是否为 JSON 格式
  1420. if not request.is_json:
  1421. return jsonify({
  1422. 'success': False,
  1423. 'message': '请求必须是 JSON 格式',
  1424. 'data': None
  1425. }), 400
  1426. # 获取请求数据
  1427. data = request.get_json()
  1428. # 检查是否提供了 talent_list 参数
  1429. if 'talent_list' not in data:
  1430. return jsonify({
  1431. 'success': False,
  1432. 'message': '缺少必填参数: talent_list',
  1433. 'data': None
  1434. }), 400
  1435. # 检查是否提供了 web_md 参数
  1436. if 'web_md' not in data:
  1437. return jsonify({
  1438. 'success': False,
  1439. 'message': '缺少必填参数: web_md',
  1440. 'data': None
  1441. }), 400
  1442. talent_list = data['talent_list']
  1443. web_md = data['web_md']
  1444. # 验证 talent_list 是否为数组
  1445. if not isinstance(talent_list, list):
  1446. return jsonify({
  1447. 'success': False,
  1448. 'message': 'talent_list 必须是数组类型',
  1449. 'data': None
  1450. }), 400
  1451. # 验证 web_md 是否为字符串
  1452. if not isinstance(web_md, str):
  1453. return jsonify({
  1454. 'success': False,
  1455. 'message': 'web_md 必须是字符串类型',
  1456. 'data': None
  1457. }), 400
  1458. # 验证数组不能为空
  1459. if len(talent_list) == 0:
  1460. return jsonify({
  1461. 'success': False,
  1462. 'message': 'talent_list 不能为空数组',
  1463. 'data': None
  1464. }), 400
  1465. # 验证文本内容不能为空
  1466. if not web_md.strip():
  1467. return jsonify({
  1468. 'success': False,
  1469. 'message': 'web_md 内容不能为空',
  1470. 'data': None
  1471. }), 400
  1472. # 验证文本长度(防止过长的文本)
  1473. if len(web_md) > 100000: # 限制最大100KB
  1474. return jsonify({
  1475. 'success': False,
  1476. 'message': 'web_md 内容过长,最大支持100KB',
  1477. 'data': None
  1478. }), 400
  1479. # 验证数组长度(防止过多记录)
  1480. if len(talent_list) > 50: # 限制最大50条记录
  1481. return jsonify({
  1482. 'success': False,
  1483. 'message': 'talent_list 记录过多,最大支持50条记录',
  1484. 'data': None
  1485. }), 400
  1486. # 基本的数据格式验证
  1487. for index, talent_data in enumerate(talent_list):
  1488. if not isinstance(talent_data, dict):
  1489. return jsonify({
  1490. 'success': False,
  1491. 'message': f'talent_list 第{index + 1}项必须是对象类型',
  1492. 'data': None
  1493. }), 400
  1494. # 检查必要的字段
  1495. if not talent_data.get('name_zh'):
  1496. return jsonify({
  1497. 'success': False,
  1498. 'message': f'talent_list 第{index + 1}项缺少必填字段: name_zh',
  1499. 'data': None
  1500. }), 400
  1501. # 记录处理请求
  1502. logger.info(f"开始处理网页人才信息,人才数量: {len(talent_list)}, 网页内容长度: {len(web_md)} 字符")
  1503. # 调用网页人才处理函数
  1504. result = add_webpage_talent(talent_list, web_md)
  1505. # 根据处理结果设置HTTP状态码
  1506. if result['success']:
  1507. if result['code'] == 200:
  1508. status_code = 200 # 全部成功
  1509. elif result['code'] == 206:
  1510. status_code = 206 # 部分成功
  1511. else:
  1512. status_code = 200 # 默认成功
  1513. else:
  1514. if result['code'] == 400:
  1515. status_code = 400 # 参数错误
  1516. else:
  1517. status_code = 500 # 服务器错误
  1518. return jsonify(result), status_code
  1519. except Exception as e:
  1520. # 记录错误日志
  1521. error_msg = f"添加网页人才信息失败: {str(e)}"
  1522. logger.error(error_msg, exc_info=True)
  1523. # 返回错误响应
  1524. return jsonify({
  1525. 'success': False,
  1526. 'message': error_msg,
  1527. 'data': None
  1528. }), 500
  1529. # 获取解析任务列表接口
  1530. @bp.route('/get-parse-tasks', methods=['GET'])
  1531. def get_parse_tasks_route():
  1532. """
  1533. 获取解析任务列表的API接口,支持分页
  1534. 查询参数:
  1535. - page: 页码,从1开始,默认为1
  1536. - per_page: 每页记录数,默认为10,最大100
  1537. - task_type: 任务类型过滤,可选
  1538. - task_status: 任务状态过滤,可选
  1539. 返回:
  1540. - JSON: 包含解析任务列表和分页信息
  1541. 功能说明:
  1542. - 支持分页查询,每页默认10条记录
  1543. - 支持按任务类型和状态过滤
  1544. - 按创建时间倒序排列
  1545. - 返回总记录数和分页信息
  1546. 状态码:
  1547. - 200: 查询成功
  1548. - 400: 请求参数错误
  1549. - 500: 查询失败
  1550. """
  1551. try:
  1552. # 获取查询参数
  1553. page = request.args.get('page', 1, type=int)
  1554. per_page = request.args.get('per_page', 10, type=int)
  1555. task_type = request.args.get('task_type', type=str)
  1556. task_status = request.args.get('task_status', type=str)
  1557. # 记录请求日志
  1558. logger.info(f"获取解析任务列表请求: page={page}, per_page={per_page}, task_type={task_type}, task_status={task_status}")
  1559. # 调用核心业务逻辑
  1560. result = get_parse_tasks(page, per_page, task_type, task_status)
  1561. # 返回结果
  1562. return jsonify({
  1563. 'success': result['success'],
  1564. 'message': result['message'],
  1565. 'data': result['data']
  1566. }), result['code']
  1567. except Exception as e:
  1568. # 记录错误日志
  1569. error_msg = f"获取解析任务列表接口失败: {str(e)}"
  1570. logger.error(error_msg, exc_info=True)
  1571. # 返回错误响应
  1572. return jsonify({
  1573. 'success': False,
  1574. 'message': error_msg,
  1575. 'data': None
  1576. }), 500
  1577. # 获取解析任务详情接口
  1578. @bp.route('/get-parse-task-detail', methods=['GET'])
  1579. def get_parse_task_detail_route():
  1580. """
  1581. 获取解析任务详情的API接口
  1582. 查询参数:
  1583. - task_name: 任务名称,必填
  1584. 返回:
  1585. - JSON: 包含任务详细信息
  1586. 功能说明:
  1587. - 根据任务名称查询指定任务的详细信息
  1588. - 返回任务的所有字段信息
  1589. - 包含解析结果的完整数据
  1590. 状态码:
  1591. - 200: 查询成功
  1592. - 400: 请求参数错误
  1593. - 404: 任务不存在
  1594. - 500: 查询失败
  1595. """
  1596. try:
  1597. # 获取查询参数
  1598. task_name = request.args.get('task_name', type=str)
  1599. # 参数验证
  1600. if not task_name:
  1601. return jsonify({
  1602. 'success': False,
  1603. 'message': '任务名称参数不能为空',
  1604. 'data': None
  1605. }), 400
  1606. # 记录请求日志
  1607. logger.info(f"获取解析任务详情请求: task_name={task_name}")
  1608. # 调用核心业务逻辑
  1609. result = get_parse_task_detail(task_name)
  1610. # 返回结果
  1611. return jsonify({
  1612. 'success': result['success'],
  1613. 'message': result['message'],
  1614. 'data': result['data']
  1615. }), result['code']
  1616. except Exception as e:
  1617. # 记录错误日志
  1618. error_msg = f"获取解析任务详情接口失败: {str(e)}"
  1619. logger.error(error_msg, exc_info=True)
  1620. # 返回错误响应
  1621. return jsonify({
  1622. 'success': False,
  1623. 'message': error_msg,
  1624. 'data': None
  1625. }), 500
  1626. # 新增解析任务接口
  1627. @bp.route('/add-parse-task', methods=['POST'])
  1628. def add_parse_task_route():
  1629. """
  1630. 新增解析任务的API接口
  1631. 请求参数:
  1632. - task_type: 任务类型 (form-data字段,必填)
  1633. 可选值:'名片', '简历', '新任命', '招聘', '杂项'
  1634. - files: 文件数组 (multipart/form-data,对于招聘类型可选)
  1635. - created_by: 创建者 (可选,form-data字段)
  1636. 返回:
  1637. - JSON: 包含任务创建结果和上传摘要
  1638. 功能说明:
  1639. - 根据任务类型处理不同格式的文件
  1640. - 名片任务:JPG/PNG格式图片 → talent_photos目录
  1641. - 简历任务:PDF格式文件 → resume_files目录
  1642. - 新任命任务:MD格式文件 → appointment_files目录
  1643. - 招聘任务:数据库记录处理,无需文件上传
  1644. - 杂项任务:任意格式文件 → misc_files目录
  1645. - 使用timestamp+uuid自动生成文件名
  1646. - 在parse_task_repository表中创建待解析任务记录
  1647. 状态码:
  1648. - 200: 所有文件上传成功,任务创建成功
  1649. - 206: 部分文件上传成功,任务创建成功
  1650. - 400: 请求参数错误
  1651. - 500: 服务器内部错误
  1652. """
  1653. try:
  1654. # 获取任务类型参数
  1655. task_type = request.form.get('task_type')
  1656. # 验证任务类型
  1657. if not task_type:
  1658. return jsonify({
  1659. 'success': False,
  1660. 'message': '缺少task_type参数',
  1661. 'data': None
  1662. }), 400
  1663. if task_type not in ['名片', '简历', '新任命', '招聘', '杂项']:
  1664. return jsonify({
  1665. 'success': False,
  1666. 'message': 'task_type参数必须是以下值之一:名片、简历、新任命、招聘、杂项',
  1667. 'data': None
  1668. }), 400
  1669. # 获取创建者信息(可选参数)
  1670. created_by = request.form.get('created_by', 'api_user')
  1671. # 对于招聘类型,不需要文件上传
  1672. if task_type == '招聘':
  1673. # 检查是否误传了文件
  1674. if 'files' in request.files and request.files.getlist('files'):
  1675. return jsonify({
  1676. 'success': False,
  1677. 'message': '招聘类型任务不需要上传文件',
  1678. 'data': None
  1679. }), 400
  1680. # 记录请求日志
  1681. logger.info(f"新增招聘任务请求: 创建者={created_by}")
  1682. # 调用核心业务逻辑
  1683. result = add_parse_task(None, task_type, created_by)
  1684. else:
  1685. # 其他类型需要文件上传
  1686. if 'files' not in request.files:
  1687. return jsonify({
  1688. 'success': False,
  1689. 'message': f'{task_type}任务需要上传文件,请使用files字段上传文件',
  1690. 'data': None
  1691. }), 400
  1692. # 获取上传的文件列表
  1693. uploaded_files = request.files.getlist('files')
  1694. # 检查文件列表是否为空
  1695. if not uploaded_files or len(uploaded_files) == 0:
  1696. return jsonify({
  1697. 'success': False,
  1698. 'message': '文件数组不能为空',
  1699. 'data': None
  1700. }), 400
  1701. # 验证所有文件
  1702. valid_files = []
  1703. for i, file in enumerate(uploaded_files):
  1704. # 检查文件是否为空
  1705. if not file or file.filename == '':
  1706. return jsonify({
  1707. 'success': False,
  1708. 'message': f'第{i+1}个文件为空或未选择',
  1709. 'data': None
  1710. }), 400
  1711. valid_files.append(file)
  1712. # 记录请求日志
  1713. logger.info(f"新增{task_type}任务请求: 文件数量={len(valid_files)}, 创建者={created_by}")
  1714. # 调用核心业务逻辑
  1715. result = add_parse_task(valid_files, task_type, created_by)
  1716. # 根据处理结果设置HTTP状态码
  1717. if result['success']:
  1718. if result['code'] == 200:
  1719. status_code = 200
  1720. elif result['code'] == 206:
  1721. status_code = 206
  1722. else:
  1723. status_code = 200
  1724. else:
  1725. if result['code'] == 400:
  1726. status_code = 400
  1727. else:
  1728. status_code = 500
  1729. # 返回结果
  1730. return jsonify({
  1731. 'success': result['success'],
  1732. 'message': result['message'],
  1733. 'data': result['data']
  1734. }), status_code
  1735. except Exception as e:
  1736. # 记录错误日志
  1737. error_msg = f"新增解析任务接口失败: {str(e)}"
  1738. logger.error(error_msg, exc_info=True)
  1739. # 返回错误响应
  1740. return jsonify({
  1741. 'success': False,
  1742. 'message': error_msg,
  1743. 'data': None
  1744. }), 500
  1745. @bp.route('/execute-parse-task', methods=['POST'])
  1746. def execute_parse_task():
  1747. """
  1748. 执行解析任务接口
  1749. 根据task_type参数调用相应的批量处理函数:
  1750. - 名片: batch_process_business_card_images
  1751. - 简历: batch_parse_resumes
  1752. - 新任命: batch_process_md
  1753. - 招聘: batch_process_menduner_data
  1754. - 杂项: batch_process_images
  1755. 请求参数:
  1756. - task_type (str): 任务类型,可选值:'名片', '简历', '新任命', '招聘', '杂项'
  1757. - data (list): 数据列表,根据task_type不同,数据格式不同
  1758. - publish_time (str, optional): 发布时间,仅新任命任务需要
  1759. """
  1760. try:
  1761. # 获取请求数据
  1762. data = request.get_json()
  1763. if not data:
  1764. return jsonify({
  1765. 'success': False,
  1766. 'message': '请求数据不能为空',
  1767. 'data': None
  1768. }), 400
  1769. # 获取任务类型
  1770. task_type = data.get('task_type', '').strip()
  1771. if not task_type:
  1772. return jsonify({
  1773. 'success': False,
  1774. 'message': 'task_type参数不能为空',
  1775. 'data': None
  1776. }), 400
  1777. # 获取数据列表
  1778. task_data = data.get('data')
  1779. if not task_data:
  1780. return jsonify({
  1781. 'success': False,
  1782. 'message': 'data参数不能为空',
  1783. 'data': None
  1784. }), 400
  1785. # 根据任务类型执行相应的处理函数
  1786. try:
  1787. if task_type == '名片':
  1788. # 调用名片批量处理函数
  1789. result = batch_process_business_card_images(task_data)
  1790. elif task_type == '简历':
  1791. # 调用简历批量处理函数
  1792. result = batch_parse_resumes(task_data)
  1793. elif task_type == '新任命':
  1794. # 获取发布时间参数
  1795. publish_time = data.get('publish_time', '')
  1796. if not publish_time:
  1797. return jsonify({
  1798. 'success': False,
  1799. 'message': '新任命任务需要提供publish_time参数',
  1800. 'data': None
  1801. }), 400
  1802. # 调用新任命批量处理函数
  1803. result = batch_process_md(task_data, publish_time)
  1804. elif task_type == '招聘':
  1805. # 调用招聘数据批量处理函数
  1806. result = batch_process_menduner_data(task_data)
  1807. elif task_type == '杂项':
  1808. # 调用图片批量处理函数(表格类型)
  1809. process_type = data.get('process_type', 'table')
  1810. result = batch_process_images(task_data, process_type)
  1811. else:
  1812. return jsonify({
  1813. 'success': False,
  1814. 'message': f'不支持的任务类型: {task_type},支持的类型:名片、简历、新任命、招聘、杂项',
  1815. 'data': None
  1816. }), 400
  1817. # 记录处理结果日志
  1818. if result.get('success'):
  1819. logging.info(f"执行{task_type}解析任务成功: {result.get('message', '')}")
  1820. # ===== 精简:只根据id字段唯一定位任务记录 =====
  1821. from app.core.data_parse.parse_system import db, ParseTaskRepository
  1822. task_id = data.get('id')
  1823. if task_id:
  1824. task_obj = ParseTaskRepository.query.filter_by(id=task_id).first()
  1825. if task_obj:
  1826. task_obj.task_status = '成功'
  1827. task_obj.parse_result = result.get('data')
  1828. db.session.commit()
  1829. logging.info(f"已更新解析任务记录: id={getattr(task_obj, 'id', None)}, 状态=成功")
  1830. else:
  1831. logging.error(f"执行{task_type}解析任务失败: {result.get('message', '')}")
  1832. # 确定HTTP状态码
  1833. if result.get('success'):
  1834. # 检查是否有部分成功的情况
  1835. if 'code' in result:
  1836. status_code = result['code']
  1837. elif 'summary' in result.get('data', {}):
  1838. # 检查处理摘要
  1839. summary = result['data']['summary']
  1840. if summary.get('failed_count', 0) > 0 and summary.get('success_count', 0) > 0:
  1841. status_code = 206 # 部分成功
  1842. else:
  1843. status_code = 200 # 完全成功
  1844. else:
  1845. status_code = 200
  1846. else:
  1847. status_code = 500
  1848. return jsonify({
  1849. 'success': result.get('success', False),
  1850. 'message': result.get('message', '处理完成'),
  1851. 'data': result.get('data')
  1852. }), status_code
  1853. except Exception as process_error:
  1854. error_msg = f"执行{task_type}解析任务时发生错误: {str(process_error)}"
  1855. logging.error(error_msg, exc_info=True)
  1856. return jsonify({
  1857. 'success': False,
  1858. 'message': error_msg,
  1859. 'data': None
  1860. }), 500
  1861. except Exception as e:
  1862. # 记录错误日志
  1863. error_msg = f"执行解析任务接口失败: {str(e)}"
  1864. logging.error(error_msg, exc_info=True)
  1865. # 返回错误响应
  1866. return jsonify({
  1867. 'success': False,
  1868. 'message': error_msg,
  1869. 'data': None
  1870. }), 500
  1871. @bp.route('/add-parsed-talents', methods=['POST'])
  1872. def add_parsed_talents_route():
  1873. """
  1874. 处理解析任务响应数据并写入人才信息接口
  1875. 请求参数:
  1876. - api_response_data: execute-parse-task API的完整返回数据 (JSON格式)
  1877. 请求体示例:
  1878. {
  1879. "success": true,
  1880. "message": "处理完成",
  1881. "data": {
  1882. "summary": {
  1883. "total_files": 5,
  1884. "success_count": 4,
  1885. "failed_count": 1,
  1886. "success_rate": 80.0
  1887. },
  1888. "results": [
  1889. {
  1890. "index": 0,
  1891. "success": true,
  1892. "data": {
  1893. "name_zh": "张三",
  1894. "title_zh": "经理",
  1895. "hotel_zh": "某酒店"
  1896. }
  1897. }
  1898. ],
  1899. "processed_time": "2025-01-18T10:30:00"
  1900. }
  1901. }
  1902. 返回:
  1903. - JSON: 包含批量处理结果和状态信息
  1904. 功能说明:
  1905. - 接收 execute-parse-task API 的完整返回数据
  1906. - 自动识别和处理不同格式的人才数据(单人/批量)
  1907. - 调用 add_single_talent 函数将人才信息写入 business_cards 表
  1908. - 支持新任命等包含多个人员信息的批量数据
  1909. - 提供详细的处理统计和结果追踪
  1910. - 保留原始API响应数据用于调试
  1911. 状态码:
  1912. - 200: 全部处理成功
  1913. - 206: 部分处理成功
  1914. - 400: 请求参数错误
  1915. - 500: 服务器内部错误
  1916. """
  1917. try:
  1918. # 检查请求是否为 JSON 格式
  1919. if not request.is_json:
  1920. return jsonify({
  1921. 'success': False,
  1922. 'message': '请求必须是 JSON 格式',
  1923. 'data': None
  1924. }), 400
  1925. # 获取请求数据
  1926. api_response_data = request.get_json()
  1927. # 基本参数验证
  1928. if not api_response_data:
  1929. return jsonify({
  1930. 'success': False,
  1931. 'message': '请求数据不能为空',
  1932. 'data': None
  1933. }), 400
  1934. # 验证数据格式
  1935. if not isinstance(api_response_data, dict):
  1936. return jsonify({
  1937. 'success': False,
  1938. 'message': '请求数据必须是JSON对象格式',
  1939. 'data': None
  1940. }), 400
  1941. # 记录请求日志
  1942. total_results = 0
  1943. if api_response_data.get('data') and api_response_data['data'].get('results'):
  1944. total_results = len(api_response_data['data']['results'])
  1945. logger.info(f"收到处理解析任务响应数据请求,包含 {total_results} 条结果记录")
  1946. # 调用核心业务逻辑
  1947. result = add_parsed_talents(api_response_data)
  1948. # 根据处理结果设置HTTP状态码
  1949. if result.get('success', False):
  1950. if result.get('code') == 200:
  1951. status_code = 200 # 全部成功
  1952. elif result.get('code') == 206:
  1953. status_code = 206 # 部分成功
  1954. else:
  1955. status_code = 200 # 默认成功
  1956. else:
  1957. if result.get('code') == 400:
  1958. status_code = 400 # 参数错误
  1959. else:
  1960. status_code = 500 # 服务器错误
  1961. # 记录处理结果日志
  1962. if result.get('success'):
  1963. data_summary = result.get('data', {}).get('summary', {})
  1964. success_count = data_summary.get('success_count', 0)
  1965. failed_count = data_summary.get('failed_count', 0)
  1966. logger.info(f"处理解析任务响应数据完成: 成功 {success_count} 条,失败 {failed_count} 条")
  1967. else:
  1968. logger.error(f"处理解析任务响应数据失败: {result.get('message', '未知错误')}")
  1969. # 返回结果
  1970. return jsonify({
  1971. 'success': result.get('success', False),
  1972. 'message': result.get('message', '处理完成'),
  1973. 'data': result.get('data')
  1974. }), status_code
  1975. except Exception as e:
  1976. # 记录错误日志
  1977. error_msg = f"处理解析任务响应数据接口失败: {str(e)}"
  1978. logger.error(error_msg, exc_info=True)
  1979. # 返回错误响应
  1980. return jsonify({
  1981. 'success': False,
  1982. 'message': error_msg,
  1983. 'data': None
  1984. }), 500