routes.py 97 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967
  1. from flask import jsonify, request, make_response, Blueprint, current_app, send_file
  2. from datetime import datetime
  3. import json
  4. from app.core.data_parse.time_utils import get_east_asia_time_naive, get_east_asia_time_str, get_east_asia_timestamp, get_east_asia_isoformat, get_east_asia_date_str
  5. from app.api.data_parse import bp
  6. from app.core.data_parse.parse_system import (
  7. update_business_card,
  8. get_business_cards,
  9. update_business_card_status,
  10. create_talent_tag,
  11. get_talent_tag_list,
  12. update_talent_tag,
  13. delete_talent_tag,
  14. query_neo4j_graph,
  15. talent_get_tags,
  16. talent_update_tags,
  17. get_business_card,
  18. search_business_cards_by_mobile,
  19. get_duplicate_records,
  20. process_duplicate_record,
  21. get_duplicate_record_detail,
  22. fix_broken_duplicate_records
  23. )
  24. # 导入解析任务相关函数
  25. from app.core.data_parse.parse_task import (
  26. get_parse_tasks,
  27. get_parse_task_detail,
  28. add_parse_task,
  29. add_parsed_talents,
  30. web_url_crawl
  31. )
  32. # 导入酒店管理相关函数
  33. from app.core.data_parse.hotel_management import (
  34. get_hotel_positions_list,
  35. add_hotel_positions,
  36. update_hotel_positions,
  37. query_hotel_positions,
  38. delete_hotel_positions,
  39. get_hotel_group_brands_list,
  40. add_hotel_group_brands,
  41. update_hotel_group_brands,
  42. query_hotel_group_brands,
  43. delete_hotel_group_brands
  44. )
  45. # 导入名片处理函数
  46. from app.core.data_parse.parse_card import delete_business_card, batch_process_business_card_images
  47. # 导入网页文本解析函数
  48. from app.core.data_parse.parse_web import batch_process_md
  49. # 导入简历解析函数
  50. from app.core.data_parse.parse_resume import batch_parse_resumes
  51. # 导入门墩儿数据处理函数
  52. from app.core.data_parse.parse_menduner import batch_process_menduner_data
  53. # 导入图片批量处理函数
  54. from app.core.data_parse.parse_pic import batch_process_images
  55. # 导入日历相关函数
  56. from app.core.data_parse.calendar import get_calendar_by_date
  57. # 导入微信认证相关函数
  58. from app.core.data_parse.calendar import (
  59. register_wechat_user,
  60. login_wechat_user,
  61. logout_wechat_user,
  62. get_wechat_user_info,
  63. update_wechat_user_info,
  64. save_calendar_record,
  65. get_calendar_record
  66. )
  67. from app.config.config import DevelopmentConfig, ProductionConfig
  68. import logging
  69. import boto3
  70. from botocore.config import Config
  71. from botocore.exceptions import ClientError
  72. from io import BytesIO
  73. import base64
  74. import os
  75. import urllib.parse
  76. from minio import Minio
  77. from app.models.parse_models import ParseTaskRepository
  78. from app.core.data_parse.parse_system import db
  79. # Define logger
  80. logger = logging.getLogger(__name__)
  81. # For failure responses
  82. def failed(message, code=500):
  83. return {
  84. 'success': False,
  85. 'message': message,
  86. 'data': None
  87. }, code
  88. # 根据环境选择配置
  89. if os.environ.get('FLASK_ENV') == 'production':
  90. config = ProductionConfig()
  91. else:
  92. config = DevelopmentConfig()
  93. # 使用配置变量
  94. minio_url = f"{'https' if config.MINIO_SECURE else 'http'}://{config.MINIO_HOST}"
  95. minio_access_key = config.MINIO_USER
  96. minio_secret_key = config.MINIO_PASSWORD
  97. minio_bucket = config.MINIO_BUCKET
  98. use_ssl = config.MINIO_SECURE
  99. def get_minio_client():
  100. """获取 MinIO 客户端实例"""
  101. return Minio(
  102. '192.168.3.143:9000',
  103. access_key=config.MINIO_USER,
  104. secret_key=config.MINIO_PASSWORD,
  105. secure=config.MINIO_SECURE
  106. )
  107. # 更新名片信息接口
  108. @bp.route('/business-cards/<int:card_id>', methods=['PUT'])
  109. def update_business_card_route(card_id):
  110. """
  111. 更新名片信息的API接口
  112. 路径参数:
  113. - card_id: 名片记录ID
  114. 请求参数:
  115. - JSON格式的名片信息
  116. 返回:
  117. - JSON: 包含更新后的名片信息和处理状态
  118. """
  119. # 获取请求数据
  120. data = request.json
  121. if not data:
  122. return jsonify({
  123. 'success': False,
  124. 'message': '请求数据为空',
  125. 'data': None
  126. }), 400
  127. # 调用业务逻辑函数处理更新
  128. result = update_business_card(card_id, data)
  129. # 根据处理结果设置HTTP状态码
  130. status_code = 200 if result['success'] else 500
  131. if 'not found' in result.get('message', '').lower() or '未找到' in result.get('message', ''):
  132. status_code = 404
  133. return jsonify(result), status_code
  134. # 获取所有名片记录的API接口
  135. @bp.route('/get-business-cards', methods=['GET'])
  136. def get_business_cards_route():
  137. """
  138. 获取所有名片记录的API接口
  139. 返回:
  140. - JSON: 包含名片记录列表和处理状态
  141. """
  142. # 调用业务逻辑函数获取名片列表
  143. result = get_business_cards()
  144. # 根据处理结果设置HTTP状态码
  145. status_code = 200 if result['success'] else 500
  146. return jsonify(result), status_code
  147. @bp.route('/update-business-cards/<int:card_id>/status', methods=['PUT'])
  148. def update_business_card_status_route(card_id):
  149. """
  150. 更新名片状态的API接口
  151. 路径参数:
  152. - card_id: 名片记录ID
  153. 请求参数:
  154. - JSON格式,包含status字段
  155. 返回:
  156. - JSON: 包含更新后的名片信息和处理状态
  157. """
  158. # 获取请求数据
  159. data = request.json
  160. if not data or 'status' not in data:
  161. return jsonify({
  162. 'success': False,
  163. 'message': '请求数据为空或缺少status字段',
  164. 'data': None
  165. }), 400
  166. status = data['status']
  167. # 调用业务逻辑函数处理状态更新
  168. result = update_business_card_status(card_id, status)
  169. # 根据处理结果设置HTTP状态码
  170. status_code = 200 if result['success'] else 500
  171. if 'not found' in result.get('message', '').lower() or '未找到' in result.get('message', ''):
  172. status_code = 404
  173. return jsonify(result), status_code
  174. # 从MinIO获取名片图片的API接口
  175. @bp.route('/business-cards/image/<path:image_path>', methods=['GET'])
  176. def get_business_card_image(image_path):
  177. """
  178. 从MinIO获取名片图片的API接口
  179. 路径参数:
  180. - image_path: MinIO中的图片路径
  181. 返回:
  182. - 图片数据流
  183. """
  184. try:
  185. # 记录下载请求信息,便于调试
  186. logger.info(f"获取名片图片请求: {image_path}")
  187. # 获取 MinIO 客户端
  188. minio_client = get_minio_client()
  189. if not minio_client:
  190. return jsonify(failed("MinIO客户端初始化失败")), 500
  191. try:
  192. # 使用正确的MinIO客户端方法
  193. data = minio_client.get_object(minio_bucket, image_path)
  194. # 创建内存文件流
  195. file_stream = BytesIO(data.read())
  196. # 获取文件名
  197. file_name = image_path.split('/')[-1]
  198. # 返回文件
  199. return send_file(
  200. file_stream,
  201. as_attachment=False, # 设置为False,让浏览器直接显示图片
  202. download_name=file_name,
  203. mimetype='image/jpeg' # 根据实际图片类型设置
  204. )
  205. except Exception as e:
  206. logger.error(f"MinIO获取文件失败: {str(e)}")
  207. return jsonify(failed(f"文件获取失败: {str(e)}")), 404
  208. except Exception as e:
  209. logger.error(f"文件下载失败: {str(e)}")
  210. return jsonify(failed(str(e))), 500
  211. finally:
  212. # 确保关闭数据流
  213. if 'data' in locals():
  214. data.close()
  215. # 创建人才标签接口
  216. @bp.route('/create-talent-tag', methods=['POST'])
  217. def create_talent_tag_route():
  218. """
  219. 创建人才标签的API接口
  220. 请求参数:
  221. - JSON格式,包含以下字段:
  222. - name_zh: 标签名称
  223. - category: 标签分类
  224. - description: 标签描述
  225. - status: 启用状态,默认为'active'
  226. 返回:
  227. - JSON: 包含创建结果和标签信息
  228. """
  229. try:
  230. # 获取请求数据
  231. data = request.get_json()
  232. if not data:
  233. return jsonify({
  234. 'success': False,
  235. 'message': '请求数据为空',
  236. 'data': None
  237. }), 400
  238. # 验证必要字段
  239. if 'name_zh' not in data or not data['name_zh']:
  240. return jsonify({
  241. 'success': False,
  242. 'message': '标签名称不能为空',
  243. 'data': None
  244. }), 400
  245. # 处理分类字段,如果未提供则设置默认值
  246. if 'category' not in data or not data['category']:
  247. data['category'] = '未分类'
  248. # 调用业务逻辑函数处理创建
  249. result = create_talent_tag(data)
  250. # 根据处理结果设置HTTP状态码
  251. status_code = 200 if result['success'] else 500
  252. return jsonify(result), status_code
  253. except Exception as e:
  254. logger.error(f"创建人才标签失败: {str(e)}")
  255. return jsonify({
  256. 'success': False,
  257. 'message': f'创建人才标签失败: {str(e)}',
  258. 'data': None
  259. }), 500
  260. # 获取人才标签列表接口
  261. @bp.route('/get-talent-tag-list', methods=['GET'])
  262. def get_talent_tag_list_route():
  263. """
  264. 获取人才标签列表的API接口
  265. 返回:
  266. - JSON: 包含人才标签列表和处理状态
  267. """
  268. try:
  269. # 调用业务逻辑函数获取人才标签列表
  270. result = get_talent_tag_list()
  271. # 根据处理结果设置HTTP状态码
  272. status_code = 200 if result['success'] else 500
  273. return jsonify(result), status_code
  274. except Exception as e:
  275. logger.error(f"获取人才标签列表失败: {str(e)}")
  276. return jsonify({
  277. 'success': False,
  278. 'message': f'获取人才标签列表失败: {str(e)}',
  279. 'data': []
  280. }), 500
  281. # 更新人才标签接口
  282. @bp.route('/update-talent-tag/<int:tag_id>', methods=['PUT'])
  283. def update_talent_tag_route(tag_id):
  284. """
  285. 更新人才标签的API接口
  286. 路径参数:
  287. - tag_id: 标签节点ID
  288. 请求参数:
  289. - JSON格式,可能包含以下字段:
  290. - name_zh: 标签名称
  291. - category: 标签分类
  292. - description: 标签描述
  293. - status: 启用状态
  294. 返回:
  295. - JSON: 包含更新结果和标签信息
  296. """
  297. try:
  298. # 获取请求数据
  299. data = request.get_json()
  300. if not data:
  301. return jsonify({
  302. 'success': False,
  303. 'message': '请求数据为空',
  304. 'data': None
  305. }), 400
  306. # 调用业务逻辑函数处理更新
  307. result = update_talent_tag(tag_id, data)
  308. # 根据处理结果设置HTTP状态码
  309. if not result['success']:
  310. if result['code'] == 404:
  311. status_code = 404
  312. elif result['code'] == 400:
  313. status_code = 400
  314. else:
  315. status_code = 500
  316. else:
  317. status_code = 200
  318. return jsonify(result), status_code
  319. except Exception as e:
  320. logger.error(f"更新人才标签失败: {str(e)}")
  321. return jsonify({
  322. 'success': False,
  323. 'message': f'更新人才标签失败: {str(e)}',
  324. 'data': None
  325. }), 500
  326. # 删除人才标签接口
  327. @bp.route('/delete-talent-tag/<int:tag_id>', methods=['DELETE'])
  328. def delete_talent_tag_route(tag_id):
  329. """
  330. 删除人才标签的API接口
  331. 路径参数:
  332. - tag_id: 标签节点ID
  333. 返回:
  334. - JSON: 包含删除结果和被删除的标签信息
  335. """
  336. try:
  337. # 调用业务逻辑函数执行删除
  338. result = delete_talent_tag(tag_id)
  339. # 根据处理结果设置HTTP状态码
  340. if not result['success']:
  341. if result['code'] == 404:
  342. status_code = 404
  343. else:
  344. status_code = 500
  345. else:
  346. status_code = 200
  347. return jsonify(result), status_code
  348. except Exception as e:
  349. logger.error(f"删除人才标签失败: {str(e)}")
  350. return jsonify({
  351. 'success': False,
  352. 'message': f'删除人才标签失败: {str(e)}',
  353. 'data': None
  354. }), 500
  355. @bp.route('/query-kg', methods=['POST'])
  356. def query_kg():
  357. """
  358. 查询知识图谱API接口
  359. 请求参数:
  360. - query_requirement: 查询需求描述(JSON格式)
  361. 返回:
  362. - JSON: 包含查询结果和处理状态
  363. """
  364. try:
  365. # 获取请求数据
  366. data = request.json
  367. if not data or 'query_requirement' not in data:
  368. return jsonify({
  369. 'code': 400,
  370. 'success': False,
  371. 'message': '请求数据为空或缺少query_requirement字段',
  372. 'data': []
  373. }), 400
  374. query_requirement = data['query_requirement']
  375. # 调用业务逻辑函数执行查询
  376. result = query_neo4j_graph(query_requirement)
  377. # 根据处理结果设置HTTP状态码
  378. status_code = 200 if result['success'] else 500
  379. return jsonify(result), status_code
  380. except Exception as e:
  381. logger.error(f"查询知识图谱失败: {str(e)}")
  382. return jsonify({
  383. 'code': 500,
  384. 'success': False,
  385. 'message': f"查询知识图谱失败: {str(e)}",
  386. 'data': []
  387. }), 500
  388. @bp.route('/talent-get-tags/<int:talent_id>', methods=['GET'])
  389. def talent_get_tags_route(talent_id):
  390. """
  391. 获取人才标签的API接口
  392. 路径参数:
  393. - talent_id: 人才节点ID
  394. 返回:
  395. - JSON: 包含人才关联的标签列表和处理状态
  396. """
  397. try:
  398. # 调用业务逻辑函数获取人才标签
  399. result = talent_get_tags(talent_id)
  400. # 根据处理结果设置HTTP状态码
  401. status_code = 200 if result['success'] else 500
  402. return jsonify(result), status_code
  403. except Exception as e:
  404. logger.error(f"获取人才标签失败: {str(e)}")
  405. return jsonify({
  406. 'code': 500,
  407. 'success': False,
  408. 'message': f"获取人才标签失败: {str(e)}",
  409. 'data': []
  410. }), 500
  411. @bp.route('/talent-update-tags', methods=['POST'])
  412. def talent_update_tags_route():
  413. """
  414. 更新人才标签关系的API接口
  415. 请求参数:
  416. - JSON数组,包含talent和tag字段的对象列表
  417. 例如: [
  418. {"talent": 12345, "tag": "市场营销"},
  419. {"talent": 12345, "tag": "酒店管理"}
  420. ]
  421. 返回:
  422. - JSON: 包含更新结果的状态信息
  423. """
  424. try:
  425. # 获取请求数据
  426. data = request.json
  427. if not data:
  428. return jsonify({
  429. 'code': 400,
  430. 'success': False,
  431. 'message': '请求数据为空',
  432. 'data': None
  433. }), 400
  434. # 调用业务逻辑函数处理标签关系更新
  435. result = talent_update_tags(data)
  436. # 根据处理结果设置HTTP状态码
  437. if result['code'] == 200:
  438. status_code = 200
  439. elif result['code'] == 206:
  440. status_code = 206 # Partial Content
  441. elif result['code'] == 400:
  442. status_code = 400 # Bad Request
  443. elif result['code'] == 404:
  444. status_code = 404 # Not Found
  445. else:
  446. status_code = 500 # Internal Server Error
  447. return jsonify(result), status_code
  448. except Exception as e:
  449. logger.error(f"更新人才标签关系失败: {str(e)}")
  450. return jsonify({
  451. 'code': 500,
  452. 'success': False,
  453. 'message': f"更新人才标签关系失败: {str(e)}",
  454. 'data': None
  455. }), 500
  456. # 获取单个名片记录的API接口
  457. @bp.route('/get-business-card/<int:card_id>', methods=['GET'])
  458. def get_business_card_route(card_id):
  459. """
  460. 获取单个名片记录的API接口
  461. 路径参数:
  462. - card_id: 名片记录ID
  463. 返回:
  464. - JSON: 包含名片记录信息和处理状态
  465. """
  466. # 调用业务逻辑函数获取名片记录
  467. result = get_business_card(card_id)
  468. # 根据处理结果设置HTTP状态码
  469. if not result['success']:
  470. if result['code'] == 404:
  471. status_code = 404
  472. else:
  473. status_code = 500
  474. else:
  475. status_code = 200
  476. return jsonify(result), status_code
  477. @bp.route('/search-business-cards-by-mobile', methods=['GET'])
  478. def search_business_cards_by_mobile_route():
  479. """
  480. 根据手机号码搜索名片记录的API接口
  481. 查询参数:
  482. - mobile: 要搜索的手机号码
  483. 返回:
  484. - JSON: 包含搜索到的名片记录列表和处理状态
  485. 示例:
  486. GET /search-business-cards-by-mobile?mobile=13800138000
  487. """
  488. try:
  489. # 获取查询参数
  490. mobile_number = request.args.get('mobile', '').strip()
  491. if not mobile_number:
  492. return jsonify({
  493. 'success': False,
  494. 'message': '请提供要搜索的手机号码',
  495. 'data': []
  496. }), 400
  497. # 调用业务逻辑函数搜索名片记录
  498. result = search_business_cards_by_mobile(mobile_number)
  499. # 根据处理结果设置HTTP状态码
  500. if result['code'] == 200:
  501. status_code = 200
  502. elif result['code'] == 400:
  503. status_code = 400
  504. else:
  505. status_code = 500
  506. return jsonify(result), status_code
  507. except Exception as e:
  508. # 处理未预期的异常
  509. error_msg = f"根据手机号码搜索名片时发生错误: {str(e)}"
  510. logger.error(error_msg, exc_info=True)
  511. return jsonify({
  512. 'success': False,
  513. 'message': error_msg,
  514. 'data': []
  515. }), 500
  516. @bp.route('/get-hotel-positions-list', methods=['GET'])
  517. def get_hotel_positions_list_route():
  518. """
  519. 获取酒店职位数据表全部记录的API接口
  520. 返回:
  521. - JSON: 包含酒店职位记录列表和处理状态
  522. """
  523. try:
  524. # 调用业务逻辑函数获取酒店职位列表
  525. result = get_hotel_positions_list()
  526. # 根据处理结果设置HTTP状态码
  527. status_code = 200 if result['success'] else 500
  528. return jsonify(result), status_code
  529. except Exception as e:
  530. # 处理未预期的异常
  531. error_msg = f"获取酒店职位列表时发生错误: {str(e)}"
  532. logger.error(error_msg, exc_info=True)
  533. return jsonify({
  534. 'success': False,
  535. 'message': error_msg,
  536. 'data': [],
  537. 'count': 0
  538. }), 500
  539. @bp.route('/add-hotel-positions', methods=['POST'])
  540. def add_hotel_positions_route():
  541. """
  542. 新增酒店职位数据表记录的API接口
  543. 请求参数:
  544. - JSON格式,包含以下字段:
  545. - department_zh: 部门中文名称 (必填)
  546. - department_en: 部门英文名称 (必填)
  547. - position_zh: 职位中文名称 (必填)
  548. - position_en: 职位英文名称 (必填)
  549. - position_abbr: 职位英文缩写 (可选)
  550. - level_zh: 职级中文名称 (必填)
  551. - level_en: 职级英文名称 (必填)
  552. - created_by: 创建者 (可选)
  553. - updated_by: 更新者 (可选)
  554. - status: 状态 (可选)
  555. 返回:
  556. - JSON: 包含创建结果和职位信息
  557. """
  558. try:
  559. # 获取请求数据
  560. data = request.get_json()
  561. if not data:
  562. return jsonify({
  563. 'success': False,
  564. 'message': '请求数据为空',
  565. 'data': None
  566. }), 400
  567. # 调用业务逻辑函数处理创建
  568. result = add_hotel_positions(data)
  569. # 根据处理结果设置HTTP状态码
  570. if result['code'] == 200:
  571. status_code = 201 # Created
  572. elif result['code'] == 400:
  573. status_code = 400 # Bad Request
  574. elif result['code'] == 409:
  575. status_code = 409 # Conflict
  576. else:
  577. status_code = 500 # Internal Server Error
  578. return jsonify(result), status_code
  579. except Exception as e:
  580. # 处理未预期的异常
  581. error_msg = f"创建酒店职位记录时发生错误: {str(e)}"
  582. logger.error(error_msg, exc_info=True)
  583. return jsonify({
  584. 'success': False,
  585. 'message': error_msg,
  586. 'data': None
  587. }), 500
  588. @bp.route('/update-hotel-positions/<int:position_id>', methods=['PUT'])
  589. def update_hotel_positions_route(position_id):
  590. """
  591. 修改酒店职位数据表记录的API接口
  592. 路径参数:
  593. - position_id: 职位记录ID
  594. 请求参数:
  595. - JSON格式,可能包含以下字段:
  596. - department_zh: 部门中文名称
  597. - department_en: 部门英文名称
  598. - position_zh: 职位中文名称
  599. - position_en: 职位英文名称
  600. - position_abbr: 职位英文缩写
  601. - level_zh: 职级中文名称
  602. - level_en: 职级英文名称
  603. - updated_by: 更新者
  604. - status: 状态
  605. 返回:
  606. - JSON: 包含更新结果和职位信息
  607. """
  608. try:
  609. # 获取请求数据
  610. data = request.get_json()
  611. if not data:
  612. return jsonify({
  613. 'success': False,
  614. 'message': '请求数据为空',
  615. 'data': None
  616. }), 400
  617. # 调用业务逻辑函数处理更新
  618. result = update_hotel_positions(position_id, data)
  619. # 根据处理结果设置HTTP状态码
  620. if result['code'] == 200:
  621. status_code = 200 # OK
  622. elif result['code'] == 400:
  623. status_code = 400 # Bad Request
  624. elif result['code'] == 404:
  625. status_code = 404 # Not Found
  626. elif result['code'] == 409:
  627. status_code = 409 # Conflict
  628. else:
  629. status_code = 500 # Internal Server Error
  630. return jsonify(result), status_code
  631. except Exception as e:
  632. # 处理未预期的异常
  633. error_msg = f"更新酒店职位记录时发生错误: {str(e)}"
  634. logger.error(error_msg, exc_info=True)
  635. return jsonify({
  636. 'success': False,
  637. 'message': error_msg,
  638. 'data': None
  639. }), 500
  640. @bp.route('/query-hotel-positions/<int:position_id>', methods=['GET'])
  641. def query_hotel_positions_route(position_id):
  642. """
  643. 查找指定ID的酒店职位数据表记录的API接口
  644. 路径参数:
  645. - position_id: 职位记录ID
  646. 返回:
  647. - JSON: 包含查找结果和职位信息
  648. """
  649. try:
  650. # 调用业务逻辑函数查找职位记录
  651. result = query_hotel_positions(position_id)
  652. # 根据处理结果设置HTTP状态码
  653. if result['code'] == 200:
  654. status_code = 200 # OK
  655. elif result['code'] == 404:
  656. status_code = 404 # Not Found
  657. else:
  658. status_code = 500 # Internal Server Error
  659. return jsonify(result), status_code
  660. except Exception as e:
  661. # 处理未预期的异常
  662. error_msg = f"查找酒店职位记录时发生错误: {str(e)}"
  663. logger.error(error_msg, exc_info=True)
  664. return jsonify({
  665. 'success': False,
  666. 'message': error_msg,
  667. 'data': None
  668. }), 500
  669. @bp.route('/delete-hotel-positions/<int:position_id>', methods=['DELETE'])
  670. def delete_hotel_positions_route(position_id):
  671. """
  672. 删除指定ID的酒店职位数据表记录的API接口
  673. 路径参数:
  674. - position_id: 职位记录ID
  675. 返回:
  676. - JSON: 包含删除结果和被删除的职位信息
  677. """
  678. try:
  679. # 调用业务逻辑函数删除职位记录
  680. result = delete_hotel_positions(position_id)
  681. # 根据处理结果设置HTTP状态码
  682. if result['code'] == 200:
  683. status_code = 200 # OK
  684. elif result['code'] == 404:
  685. status_code = 404 # Not Found
  686. else:
  687. status_code = 500 # Internal Server Error
  688. return jsonify(result), status_code
  689. except Exception as e:
  690. # 处理未预期的异常
  691. error_msg = f"删除酒店职位记录时发生错误: {str(e)}"
  692. logger.error(error_msg, exc_info=True)
  693. return jsonify({
  694. 'success': False,
  695. 'message': error_msg,
  696. 'data': None
  697. }), 500
  698. @bp.route('/get-hotel-group-brands-list', methods=['GET'])
  699. def get_hotel_group_brands_list_route():
  700. """
  701. 获取酒店集团子品牌数据表全部记录的API接口
  702. 返回:
  703. - JSON: 包含酒店集团品牌记录列表和处理状态
  704. """
  705. try:
  706. # 调用业务逻辑函数获取酒店集团品牌列表
  707. result = get_hotel_group_brands_list()
  708. # 根据处理结果设置HTTP状态码
  709. status_code = 200 if result['success'] else 500
  710. return jsonify(result), status_code
  711. except Exception as e:
  712. # 处理未预期的异常
  713. error_msg = f"获取酒店集团品牌列表时发生错误: {str(e)}"
  714. logger.error(error_msg, exc_info=True)
  715. return jsonify({
  716. 'success': False,
  717. 'message': error_msg,
  718. 'data': [],
  719. 'count': 0
  720. }), 500
  721. @bp.route('/add-hotel-group-brands', methods=['POST'])
  722. def add_hotel_group_brands_route():
  723. """
  724. 新增酒店集团子品牌数据表记录的API接口
  725. 请求参数:
  726. - JSON格式,包含以下字段:
  727. - group_name_en: 集团英文名称 (必填)
  728. - group_name_zh: 集团中文名称 (必填)
  729. - brand_name_en: 品牌英文名称 (必填)
  730. - brand_name_zh: 品牌中文名称 (必填)
  731. - positioning_level_en: 定位级别英文名称 (必填)
  732. - positioning_level_zh: 定位级别中文名称 (必填)
  733. - created_by: 创建者 (可选)
  734. - updated_by: 更新者 (可选)
  735. - status: 状态 (可选)
  736. 返回:
  737. - JSON: 包含创建结果和品牌信息
  738. """
  739. try:
  740. # 获取请求数据
  741. data = request.get_json()
  742. if not data:
  743. return jsonify({
  744. 'success': False,
  745. 'message': '请求数据为空',
  746. 'data': None
  747. }), 400
  748. # 调用业务逻辑函数处理创建
  749. result = add_hotel_group_brands(data)
  750. # 根据处理结果设置HTTP状态码
  751. if result['code'] == 200:
  752. status_code = 201 # Created
  753. elif result['code'] == 400:
  754. status_code = 400 # Bad Request
  755. elif result['code'] == 409:
  756. status_code = 409 # Conflict
  757. else:
  758. status_code = 500 # Internal Server Error
  759. return jsonify(result), status_code
  760. except Exception as e:
  761. # 处理未预期的异常
  762. error_msg = f"创建酒店集团品牌记录时发生错误: {str(e)}"
  763. logger.error(error_msg, exc_info=True)
  764. return jsonify({
  765. 'success': False,
  766. 'message': error_msg,
  767. 'data': None
  768. }), 500
  769. @bp.route('/update-hotel-group-brands/<int:brand_id>', methods=['PUT'])
  770. def update_hotel_group_brands_route(brand_id):
  771. """
  772. 修改酒店集团子品牌数据表记录的API接口
  773. 路径参数:
  774. - brand_id: 品牌记录ID
  775. 请求参数:
  776. - JSON格式,可能包含以下字段:
  777. - group_name_en: 集团英文名称
  778. - group_name_zh: 集团中文名称
  779. - brand_name_en: 品牌英文名称
  780. - brand_name_zh: 品牌中文名称
  781. - positioning_level_en: 定位级别英文名称
  782. - positioning_level_zh: 定位级别中文名称
  783. - updated_by: 更新者
  784. - status: 状态
  785. 返回:
  786. - JSON: 包含更新结果和品牌信息
  787. """
  788. try:
  789. # 获取请求数据
  790. data = request.get_json()
  791. if not data:
  792. return jsonify({
  793. 'success': False,
  794. 'message': '请求数据为空',
  795. 'data': None
  796. }), 400
  797. # 调用业务逻辑函数处理更新
  798. result = update_hotel_group_brands(brand_id, data)
  799. # 根据处理结果设置HTTP状态码
  800. if result['code'] == 200:
  801. status_code = 200 # OK
  802. elif result['code'] == 400:
  803. status_code = 400 # Bad Request
  804. elif result['code'] == 404:
  805. status_code = 404 # Not Found
  806. elif result['code'] == 409:
  807. status_code = 409 # Conflict
  808. else:
  809. status_code = 500 # Internal Server Error
  810. return jsonify(result), status_code
  811. except Exception as e:
  812. # 处理未预期的异常
  813. error_msg = f"更新酒店集团品牌记录时发生错误: {str(e)}"
  814. logger.error(error_msg, exc_info=True)
  815. return jsonify({
  816. 'success': False,
  817. 'message': error_msg,
  818. 'data': None
  819. }), 500
  820. @bp.route('/query-hotel-group-brands/<int:brand_id>', methods=['GET'])
  821. def query_hotel_group_brands_route(brand_id):
  822. """
  823. 查找指定ID的酒店集团子品牌数据表记录的API接口
  824. 路径参数:
  825. - brand_id: 品牌记录ID
  826. 返回:
  827. - JSON: 包含查找结果和品牌信息
  828. """
  829. try:
  830. # 调用业务逻辑函数查找品牌记录
  831. result = query_hotel_group_brands(brand_id)
  832. # 根据处理结果设置HTTP状态码
  833. if result['code'] == 200:
  834. status_code = 200 # OK
  835. elif result['code'] == 404:
  836. status_code = 404 # Not Found
  837. else:
  838. status_code = 500 # Internal Server Error
  839. return jsonify(result), status_code
  840. except Exception as e:
  841. # 处理未预期的异常
  842. error_msg = f"查找酒店集团品牌记录时发生错误: {str(e)}"
  843. logger.error(error_msg, exc_info=True)
  844. return jsonify({
  845. 'success': False,
  846. 'message': error_msg,
  847. 'data': None
  848. }), 500
  849. @bp.route('/delete-hotel-group-brands/<int:brand_id>', methods=['DELETE'])
  850. def delete_hotel_group_brands_route(brand_id):
  851. """
  852. 删除指定ID的酒店集团子品牌数据表记录的API接口
  853. 路径参数:
  854. - brand_id: 品牌记录ID
  855. 返回:
  856. - JSON: 包含删除结果和被删除的品牌信息
  857. """
  858. try:
  859. # 调用业务逻辑函数删除品牌记录
  860. result = delete_hotel_group_brands(brand_id)
  861. # 根据处理结果设置HTTP状态码
  862. if result['code'] == 200:
  863. status_code = 200 # OK
  864. elif result['code'] == 404:
  865. status_code = 404 # Not Found
  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. # ==================================
  879. # 重复记录处理API接口
  880. # ==================================
  881. @bp.route('/get-duplicate-records', methods=['GET'])
  882. def get_duplicate_records_route():
  883. """
  884. 获取重复记录列表的API接口
  885. 查询参数:
  886. - status: 可选,筛选特定状态的记录 ('pending', 'processed', 'ignored')
  887. 返回:
  888. - JSON: 包含重复记录列表和处理状态
  889. """
  890. try:
  891. # 获取查询参数
  892. status = request.args.get('status', None)
  893. # 验证status参数的有效性
  894. if status and status not in ['pending', 'processed', 'ignored']:
  895. return jsonify({
  896. 'success': False,
  897. 'message': 'status参数无效,必须为 pending、processed 或 ignored',
  898. 'data': None
  899. }), 400
  900. # 调用业务逻辑函数获取重复记录列表
  901. result = get_duplicate_records(status)
  902. # 根据处理结果设置HTTP状态码
  903. status_code = 200 if result['success'] else 500
  904. return jsonify(result), status_code
  905. except Exception as e:
  906. # 处理未预期的异常
  907. error_msg = f"获取重复记录列表时发生错误: {str(e)}"
  908. logger.error(error_msg, exc_info=True)
  909. return jsonify({
  910. 'success': False,
  911. 'message': error_msg,
  912. 'data': [],
  913. 'count': 0
  914. }), 500
  915. @bp.route('/process-duplicate-record/<int:duplicate_id>', methods=['POST'])
  916. def process_duplicate_record_route(duplicate_id):
  917. """
  918. 处理重复记录的API接口
  919. 路径参数:
  920. - duplicate_id: 重复记录ID
  921. 请求参数:
  922. - JSON格式,包含以下字段:
  923. - action: 处理动作 (必填) ('merge_to_suspected', 'keep_main', 'ignore')
  924. - selected_duplicate_id: 当action为'merge_to_suspected'时,选择的疑似重复记录ID (可选)
  925. - processed_by: 处理人 (可选)
  926. - notes: 处理备注 (可选)
  927. 返回:
  928. - JSON: 包含处理结果和状态信息
  929. 返回格式:
  930. {
  931. 'success': true/false,
  932. 'message': '处理结果描述'
  933. }
  934. 功能说明:
  935. - 接收包含人才数据的请求体
  936. - 严格按照样例格式处理 results 数组中的人才数据
  937. - 调用 add_single_talent 函数将人才信息写入 business_cards 表
  938. - 提供详细的处理统计和结果追踪
  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. action = data.get('action')
  951. if not action:
  952. return jsonify({
  953. 'success': False,
  954. 'message': '缺少必填字段: action',
  955. 'data': None
  956. }), 400
  957. # 验证action参数的有效性
  958. if action not in ['merge_to_suspected', 'keep_main', 'ignore']:
  959. return jsonify({
  960. 'success': False,
  961. 'message': 'action参数无效,必须为 merge_to_suspected、keep_main 或 ignore',
  962. 'data': None
  963. }), 400
  964. # 提取其他参数
  965. selected_duplicate_id = data.get('selected_duplicate_id')
  966. processed_by = data.get('processed_by')
  967. notes = data.get('notes')
  968. # 特殊验证:如果action为merge_to_suspected,必须提供selected_duplicate_id
  969. if action == 'merge_to_suspected' and not selected_duplicate_id:
  970. return jsonify({
  971. 'success': False,
  972. 'message': '执行merge_to_suspected操作时必须提供selected_duplicate_id',
  973. 'data': None
  974. }), 400
  975. # 调用业务逻辑函数处理重复记录
  976. result = process_duplicate_record(
  977. duplicate_id=duplicate_id,
  978. action=action,
  979. selected_duplicate_id=selected_duplicate_id,
  980. processed_by=processed_by,
  981. notes=notes
  982. )
  983. # 根据处理结果设置HTTP状态码
  984. if result['code'] == 200:
  985. status_code = 200 # OK
  986. elif result['code'] == 400:
  987. status_code = 400 # Bad Request
  988. elif result['code'] == 404:
  989. status_code = 404 # Not Found
  990. else:
  991. status_code = 500 # Internal Server Error
  992. return jsonify({
  993. 'success': result['success'],
  994. 'message': result['message']
  995. }), status_code
  996. except Exception as e:
  997. # 处理未预期的异常
  998. error_msg = f"处理重复记录时发生错误: {str(e)}"
  999. logger.error(error_msg, exc_info=True)
  1000. return jsonify({
  1001. 'success': False,
  1002. 'message': error_msg,
  1003. 'data': None
  1004. }), 500
  1005. @bp.route('/get-duplicate-record-detail/<int:duplicate_id>', methods=['GET'])
  1006. def get_duplicate_record_detail_route(duplicate_id):
  1007. """
  1008. 获取指定重复记录详细信息的API接口
  1009. 路径参数:
  1010. - duplicate_id: 重复记录ID
  1011. 返回:
  1012. - JSON: 包含重复记录详细信息
  1013. """
  1014. try:
  1015. # 调用业务逻辑函数获取重复记录详情
  1016. result = get_duplicate_record_detail(duplicate_id)
  1017. # 根据处理结果设置HTTP状态码
  1018. if result['code'] == 200:
  1019. status_code = 200 # OK
  1020. elif result['code'] == 404:
  1021. status_code = 404 # Not Found
  1022. else:
  1023. status_code = 500 # Internal Server Error
  1024. return jsonify(result), status_code
  1025. except Exception as e:
  1026. # 处理未预期的异常
  1027. error_msg = f"获取重复记录详情时发生错误: {str(e)}"
  1028. logger.error(error_msg, exc_info=True)
  1029. return jsonify({
  1030. 'success': False,
  1031. 'message': error_msg,
  1032. 'data': None
  1033. }), 500
  1034. # 删除名片记录接口
  1035. @bp.route('/delete-business-card/<int:card_id>', methods=['DELETE'])
  1036. def delete_business_card_route(card_id):
  1037. """
  1038. 删除名片记录的API接口
  1039. 路径参数:
  1040. - card_id: 名片记录ID (必填)
  1041. 功能说明:
  1042. - 删除PostgreSQL数据库中business_cards表的指定记录
  1043. - 删除PostgreSQL数据库中duplicate_business_cards表的相关记录
  1044. - 删除MinIO存储中的名片图片文件
  1045. - 删除Neo4j图数据库中talent节点及其关联关系
  1046. 返回:
  1047. - JSON: 包含删除操作的结果状态和被删除的记录信息
  1048. 状态码:
  1049. - 200: 完全成功删除所有相关数据
  1050. - 206: 部分成功 (PostgreSQL删除成功,但Neo4j删除失败)
  1051. - 404: 未找到指定ID的名片记录
  1052. - 500: 删除操作失败
  1053. """
  1054. try:
  1055. # 验证card_id参数
  1056. if not card_id or card_id <= 0:
  1057. return jsonify({
  1058. 'success': False,
  1059. 'message': '无效的名片记录ID',
  1060. 'data': None
  1061. }), 400
  1062. # 调用删除函数
  1063. result = delete_business_card(card_id)
  1064. # 根据处理结果设置HTTP状态码和返回响应
  1065. if result['success']:
  1066. if result['code'] == 200:
  1067. status_code = 200 # 完全成功
  1068. elif result['code'] == 206:
  1069. status_code = 206 # 部分成功
  1070. else:
  1071. status_code = 200 # 默认成功
  1072. else:
  1073. if result['code'] == 404:
  1074. status_code = 404 # 未找到记录
  1075. elif result['code'] == 400:
  1076. status_code = 400 # 参数错误
  1077. else:
  1078. status_code = 500 # 服务器错误
  1079. return jsonify(result), status_code
  1080. except Exception as e:
  1081. logger.error(f"删除名片记录失败: {str(e)}")
  1082. return jsonify({
  1083. 'success': False,
  1084. 'message': f'删除名片记录失败: {str(e)}',
  1085. 'data': None
  1086. }), 500
  1087. # 修复损坏的重复记录接口
  1088. @bp.route('/fix-broken-duplicate-records', methods=['POST'])
  1089. def fix_broken_duplicate_records_route():
  1090. """
  1091. 修复duplicate_business_cards表中main_card_id为null的损坏记录
  1092. 功能说明:
  1093. - 查找所有main_card_id为null的损坏记录
  1094. - 删除这些损坏的记录以维护数据完整性
  1095. - 返回修复操作的详细结果
  1096. 返回:
  1097. - JSON: 包含修复操作的结果和被删除记录的信息
  1098. 状态码:
  1099. - 200: 修复成功
  1100. - 500: 修复失败
  1101. 注意:
  1102. - 此操作会永久删除损坏的记录
  1103. - 建议在系统维护时执行此操作
  1104. """
  1105. try:
  1106. # 调用修复函数
  1107. result = fix_broken_duplicate_records()
  1108. # 根据结果设置状态码
  1109. if result['success']:
  1110. status_code = 200
  1111. else:
  1112. status_code = 500
  1113. return jsonify(result), status_code
  1114. except Exception as e:
  1115. logger.error(f"修复损坏记录接口调用失败: {str(e)}")
  1116. return jsonify({
  1117. 'success': False,
  1118. 'message': f'修复损坏记录接口调用失败: {str(e)}',
  1119. 'data': None
  1120. }), 500
  1121. # 获取解析任务列表接口
  1122. @bp.route('/get-parse-tasks', methods=['GET'])
  1123. def get_parse_tasks_route():
  1124. """
  1125. 获取解析任务列表的API接口,支持分页
  1126. 查询参数:
  1127. - page: 页码,从1开始,默认为1
  1128. - per_page: 每页记录数,默认为10,最大100
  1129. - task_type: 任务类型过滤,可选
  1130. - task_status: 任务状态过滤,可选
  1131. 返回:
  1132. - JSON: 包含解析任务列表和分页信息
  1133. 功能说明:
  1134. - 支持分页查询,每页默认10条记录
  1135. - 支持按任务类型和状态过滤
  1136. - 按创建时间倒序排列
  1137. - 返回总记录数和分页信息
  1138. 状态码:
  1139. - 200: 查询成功
  1140. - 400: 请求参数错误
  1141. - 500: 查询失败
  1142. """
  1143. try:
  1144. # 获取查询参数
  1145. page = request.args.get('page', 1, type=int)
  1146. per_page = request.args.get('per_page', 10, type=int)
  1147. task_type = request.args.get('task_type', type=str)
  1148. task_status = request.args.get('task_status', type=str)
  1149. # 记录请求日志
  1150. logger.info(f"获取解析任务列表请求: page={page}, per_page={per_page}, task_type={task_type}, task_status={task_status}")
  1151. # 调用核心业务逻辑
  1152. result = get_parse_tasks(page, per_page, task_type, task_status)
  1153. # 返回结果
  1154. return jsonify({
  1155. 'success': result['success'],
  1156. 'message': result['message'],
  1157. 'data': result['data']
  1158. }), result['code']
  1159. except Exception as e:
  1160. # 记录错误日志
  1161. error_msg = f"获取解析任务列表接口失败: {str(e)}"
  1162. logger.error(error_msg, exc_info=True)
  1163. # 返回错误响应
  1164. return jsonify({
  1165. 'success': False,
  1166. 'message': error_msg,
  1167. 'data': None
  1168. }), 500
  1169. # 获取解析任务详情接口
  1170. @bp.route('/get-parse-task-detail', methods=['GET'])
  1171. def get_parse_task_detail_route():
  1172. """
  1173. 获取解析任务详情的API接口
  1174. 查询参数:
  1175. - task_name: 任务名称,必填
  1176. 返回:
  1177. - JSON: 包含任务详细信息
  1178. 功能说明:
  1179. - 根据任务名称查询指定任务的详细信息
  1180. - 返回任务的所有字段信息
  1181. - 包含解析结果的完整数据
  1182. 状态码:
  1183. - 200: 查询成功
  1184. - 400: 请求参数错误
  1185. - 404: 任务不存在
  1186. - 500: 查询失败
  1187. """
  1188. try:
  1189. # 获取查询参数
  1190. task_name = request.args.get('task_name', type=str)
  1191. # 参数验证
  1192. if not task_name:
  1193. return jsonify({
  1194. 'success': False,
  1195. 'message': '任务名称参数不能为空',
  1196. 'data': None
  1197. }), 400
  1198. # 记录请求日志
  1199. logger.info(f"获取解析任务详情请求: task_name={task_name}")
  1200. # 调用核心业务逻辑
  1201. result = get_parse_task_detail(task_name)
  1202. # 返回结果
  1203. return jsonify({
  1204. 'success': result['success'],
  1205. 'message': result['message'],
  1206. 'data': result['data']
  1207. }), result['code']
  1208. except Exception as e:
  1209. # 记录错误日志
  1210. error_msg = f"获取解析任务详情接口失败: {str(e)}"
  1211. logger.error(error_msg, exc_info=True)
  1212. # 返回错误响应
  1213. return jsonify({
  1214. 'success': False,
  1215. 'message': error_msg,
  1216. 'data': None
  1217. }), 500
  1218. # 新增解析任务接口
  1219. @bp.route('/add-parse-task', methods=['POST'])
  1220. def add_parse_task_route():
  1221. """
  1222. 新增解析任务的API接口
  1223. 请求参数:
  1224. - task_type: 任务类型 (form-data字段,必填)
  1225. 可选值:'名片', '简历', '新任命', '招聘', '杂项'
  1226. - files: 文件数组 (multipart/form-data,对于招聘类型可选)
  1227. - created_by: 创建者 (可选,form-data字段)
  1228. - data: 数据内容 (form-data字段,招聘类型必填)
  1229. - publish_time: 发布时间 (form-data字段,新任命类型必填)
  1230. 返回:
  1231. - JSON: 包含任务创建结果和状态信息
  1232. 返回格式:
  1233. {
  1234. 'success': true/false,
  1235. 'message': '处理结果描述'
  1236. }
  1237. 功能说明:
  1238. - 根据任务类型处理不同格式的文件
  1239. - 名片任务:JPG/PNG格式图片 → talent_photos目录
  1240. - 简历任务:PDF格式文件 → resume_files目录
  1241. - 新任命任务:MD格式文件 → appointment_files目录
  1242. - 招聘任务:数据库记录处理,无需文件上传,创建任务后立即执行解析
  1243. - 杂项任务:任意格式文件 → misc_files目录
  1244. - 使用timestamp+uuid自动生成文件名
  1245. - 在parse_task_repository表中创建待解析任务记录
  1246. 状态码:
  1247. - 200: 所有文件上传成功,任务创建成功
  1248. - 206: 部分文件上传成功,任务创建成功
  1249. - 400: 请求参数错误
  1250. - 500: 服务器内部错误
  1251. """
  1252. try:
  1253. # 获取任务类型参数
  1254. task_type = request.form.get('task_type')
  1255. # 验证任务类型
  1256. if not task_type:
  1257. return jsonify({
  1258. 'success': False,
  1259. 'message': '缺少task_type参数'
  1260. }), 400
  1261. if task_type not in ['名片', '简历', '新任命', '招聘', '杂项']:
  1262. return jsonify({
  1263. 'success': False,
  1264. 'message': 'task_type参数必须是以下值之一:名片、简历、新任命、招聘、杂项'
  1265. }), 400
  1266. # 获取创建者信息(可选参数)
  1267. created_by = request.form.get('created_by', 'api_user')
  1268. # 获取数据内容和发布时间参数
  1269. data = request.form.get('data')
  1270. publish_time = request.form.get('publish_time')
  1271. # 对于招聘类型,不需要文件上传
  1272. if task_type == '招聘':
  1273. # 检查是否误传了文件
  1274. if 'files' in request.files and request.files.getlist('files'):
  1275. return jsonify({
  1276. 'success': False,
  1277. 'message': '招聘类型任务不需要上传文件'
  1278. }), 400
  1279. # 检查data参数是否有内容
  1280. if not data:
  1281. return jsonify({
  1282. 'success': False,
  1283. 'message': '招聘类型任务需要提供data参数'
  1284. }), 400
  1285. # 记录请求日志
  1286. logger.info(f"新增招聘任务请求: 创建者={created_by}, data长度={len(data) if data else 0}")
  1287. # 调用核心业务逻辑
  1288. result = add_parse_task(None, task_type, created_by, data, publish_time)
  1289. # 如果任务创建成功,继续执行批量处理
  1290. if result['success']:
  1291. # 招聘任务创建成功,不需要进一步处理
  1292. logger.info(f"招聘任务创建成功")
  1293. else:
  1294. logger.error(f"招聘任务创建失败: {result.get('message', '未知错误')}")
  1295. else:
  1296. # 其他类型需要文件上传
  1297. if 'files' not in request.files:
  1298. return jsonify({
  1299. 'success': False,
  1300. 'message': f'{task_type}任务需要上传文件,请使用files字段上传文件'
  1301. }), 400
  1302. # 获取上传的文件列表
  1303. uploaded_files = request.files.getlist('files')
  1304. # 检查文件列表是否为空
  1305. if not uploaded_files or len(uploaded_files) == 0:
  1306. return jsonify({
  1307. 'success': False,
  1308. 'message': '文件数组不能为空'
  1309. }), 400
  1310. # 验证所有文件
  1311. valid_files = []
  1312. for i, file in enumerate(uploaded_files):
  1313. # 检查文件是否为空
  1314. if not file or file.filename == '':
  1315. return jsonify({
  1316. 'success': False,
  1317. 'message': f'第{i+1}个文件为空或未选择'
  1318. }), 400
  1319. valid_files.append(file)
  1320. # 对于新任命类型,检查publish_time参数
  1321. if task_type == '新任命':
  1322. if not publish_time:
  1323. return jsonify({
  1324. 'success': False,
  1325. 'message': '新任命类型任务需要提供publish_time参数'
  1326. }), 400
  1327. # 记录请求日志
  1328. logger.info(f"新增{task_type}任务请求: 文件数量={len(valid_files)}, 创建者={created_by}")
  1329. # 调用核心业务逻辑
  1330. result = add_parse_task(valid_files, task_type, created_by, data, publish_time)
  1331. # 根据处理结果设置HTTP状态码
  1332. if result['success']:
  1333. if result['code'] == 200:
  1334. status_code = 200
  1335. elif result['code'] == 206:
  1336. status_code = 206
  1337. else:
  1338. status_code = 200
  1339. else:
  1340. if result['code'] == 400:
  1341. status_code = 400
  1342. else:
  1343. status_code = 500
  1344. # 返回结果
  1345. return jsonify({
  1346. 'success': result['success'],
  1347. 'message': result['message']
  1348. }), status_code
  1349. except Exception as e:
  1350. # 记录错误日志
  1351. error_msg = f"新增解析任务接口失败: {str(e)}"
  1352. logger.error(error_msg, exc_info=True)
  1353. # 返回错误响应
  1354. return jsonify({
  1355. 'success': False,
  1356. 'message': error_msg
  1357. }), 500
  1358. @bp.route('/execute-parse-task', methods=['POST'])
  1359. def execute_parse_task():
  1360. """
  1361. 执行解析任务接口
  1362. 根据task_type参数调用相应的批量处理函数:
  1363. - 名片: batch_process_business_card_images
  1364. - 简历: batch_parse_resumes
  1365. - 新任命: batch_process_md
  1366. - 招聘: 已在add-parse-task接口中自动处理,此处不再支持
  1367. - 杂项: batch_process_images
  1368. 请求参数:
  1369. - data (dict): 包含完整任务信息的对象,格式如下:
  1370. {
  1371. "id": 123,
  1372. "task_name": "parse_task_20241201_a1b2c3d4",
  1373. "task_status": "待解析",
  1374. "task_type": "名片",
  1375. "task_source": [
  1376. {
  1377. "original_filename": "张三名片.jpg",
  1378. "minio_path": "https://192.168.3.143:9000/dataops-platform/talent_photos/20241201_001234_张三名片.jpg",
  1379. "status": "正常"
  1380. }
  1381. ],
  1382. "collection_count": 2,
  1383. "parse_count": 0,
  1384. "parse_result": null,
  1385. "created_at": "2024-12-01 10:30:45",
  1386. "created_by": "api_user",
  1387. "updated_at": "2024-12-01 10:30:45",
  1388. "updated_by": "api_user"
  1389. }
  1390. 对于新任命类型,task_source中的每个对象还需要包含publish_time字段:
  1391. {
  1392. "publish_time": "20250731",
  1393. "original_filename": "张三任命.md",
  1394. "minio_path": "https://192.168.3.143:9000/dataops-platform/appointment_files/20241201_001234_张三任命.md",
  1395. "status": "正常"
  1396. }
  1397. """
  1398. try:
  1399. # 获取请求数据
  1400. request_data = request.get_json()
  1401. if not request_data:
  1402. return jsonify({
  1403. 'success': False,
  1404. 'message': '请求数据不能为空',
  1405. 'data': None
  1406. }), 400
  1407. # 验证请求数据格式
  1408. if not isinstance(request_data, dict) or 'data' not in request_data:
  1409. return jsonify({
  1410. 'success': False,
  1411. 'message': '请求数据格式错误,必须包含data字段',
  1412. 'data': None
  1413. }), 400
  1414. # 获取任务数据
  1415. task_data = request_data.get('data')
  1416. if not task_data:
  1417. return jsonify({
  1418. 'success': False,
  1419. 'message': '任务数据不能为空',
  1420. 'data': None
  1421. }), 400
  1422. # 验证任务数据格式
  1423. if not isinstance(task_data, dict):
  1424. return jsonify({
  1425. 'success': False,
  1426. 'message': '任务数据必须是对象格式',
  1427. 'data': None
  1428. }), 400
  1429. # 获取任务类型
  1430. task_type = task_data.get('task_type', '').strip()
  1431. if not task_type:
  1432. return jsonify({
  1433. 'success': False,
  1434. 'message': '任务类型不能为空',
  1435. 'data': None
  1436. }), 400
  1437. # 获取任务源数据
  1438. task_source = task_data.get('task_source', [])
  1439. if not task_source:
  1440. return jsonify({
  1441. 'success': False,
  1442. 'message': '任务源数据不能为空',
  1443. 'data': None
  1444. }), 400
  1445. # 验证任务源数据格式
  1446. if not isinstance(task_source, list):
  1447. return jsonify({
  1448. 'success': False,
  1449. 'message': '任务源数据必须是数组格式',
  1450. 'data': None
  1451. }), 400
  1452. # 获取任务ID
  1453. task_id = task_data.get('id')
  1454. # 更新parse_task_repository数据库表中的task_source
  1455. if task_id:
  1456. try:
  1457. from app.models.parse_models import ParseTaskRepository
  1458. from app.core.data_parse.parse_system import db
  1459. task_record = ParseTaskRepository.query.get(task_id)
  1460. if task_record:
  1461. task_record.task_source = task_source
  1462. task_record.updated_at = get_east_asia_time_naive()
  1463. task_record.updated_by = 'admin'
  1464. db.session.commit()
  1465. logging.info(f"已更新task_id为{task_id}的任务记录的task_source")
  1466. else:
  1467. logging.warning(f"未找到task_id为{task_id}的任务记录")
  1468. except Exception as update_error:
  1469. logging.error(f"更新任务记录失败: {str(update_error)}")
  1470. db.session.rollback()
  1471. # 根据任务类型执行相应的处理函数
  1472. try:
  1473. if task_type == '名片':
  1474. # 调用名片批量处理函数
  1475. result = batch_process_business_card_images(task_source, task_id, task_type)
  1476. elif task_type == '简历':
  1477. # 调用简历批量处理函数
  1478. result = batch_parse_resumes(task_source, task_id, task_type)
  1479. elif task_type == '新任命':
  1480. # 验证新任命任务的publish_time字段
  1481. for source_item in task_source:
  1482. if not isinstance(source_item, dict) or 'publish_time' not in source_item:
  1483. return jsonify({
  1484. 'success': False,
  1485. 'message': '新任命任务的每个源数据必须包含publish_time字段',
  1486. 'data': None
  1487. }), 400
  1488. # 调用新任命批量处理函数
  1489. result = batch_process_md(task_source, task_id=task_id, task_type=task_type)
  1490. elif task_type == '招聘':
  1491. result = batch_process_menduner_data(task_source, task_id, task_type)
  1492. elif task_type == '杂项':
  1493. # 调用图片批量处理函数(表格类型)
  1494. process_type = request_data.get('process_type', 'table')
  1495. result = batch_process_images(task_source, process_type, task_id, task_type)
  1496. else:
  1497. return jsonify({
  1498. 'success': False,
  1499. 'message': f'不支持的任务类型: {task_type},支持的类型:名片、简历、新任命、招聘、杂项',
  1500. 'data': None
  1501. }), 400
  1502. # 记录处理结果日志并更新任务状态
  1503. from app.models.parse_models import ParseTaskRepository
  1504. from app.core.data_parse.parse_system import db
  1505. task_obj = None
  1506. if task_id:
  1507. task_obj = ParseTaskRepository.query.filter_by(id=task_id).first()
  1508. # 根据解析结果确定任务状态和返回数据
  1509. if result.get('success'):
  1510. logging.info(f"执行{task_type}解析任务成功: {result.get('message', '')}")
  1511. # 获取解析结果数据
  1512. result_data = result.get('data', {})
  1513. success_count = result_data.get('success_count', 0)
  1514. failed_count = result_data.get('failed_count', 0)
  1515. # 对于新任命类型,parsed_record_ids在process_single_markdown_file中已经处理
  1516. parsed_record_ids = result_data.get('parsed_record_ids', [])
  1517. # 确定任务状态
  1518. if failed_count == 0:
  1519. task_status = '解析成功'
  1520. elif success_count > 0:
  1521. task_status = '部分解析成功'
  1522. else:
  1523. task_status = '不成功'
  1524. # 更新任务记录
  1525. if task_obj:
  1526. task_obj.task_status = task_status
  1527. task_obj.parse_count = success_count
  1528. # 对于新任命类型,需要从数据库中查询实际的记录ID
  1529. if task_type == '新任命':
  1530. try:
  1531. from app.core.data_parse.parse_system import ParsedTalent
  1532. # 查询该任务相关的所有记录
  1533. parsed_records = ParsedTalent.query.filter_by(task_id=str(task_id), task_type=task_type).all()
  1534. record_ids = [str(record.id) for record in parsed_records]
  1535. task_obj.parse_result = ','.join(record_ids) if record_ids else ''
  1536. except Exception as e:
  1537. logging.error(f"查询新任命记录ID失败: {str(e)}")
  1538. task_obj.parse_result = ''
  1539. else:
  1540. task_obj.parse_result = ','.join(parsed_record_ids) if parsed_record_ids else ''
  1541. task_obj.updated_at = get_east_asia_time_naive()
  1542. task_obj.updated_by = 'admin'
  1543. db.session.commit()
  1544. logging.info(f"已更新解析任务记录: id={getattr(task_obj, 'id', None)}, 状态={task_obj.task_status}")
  1545. # 构建返回数据,按照请求参数格式返回
  1546. return_data = task_data.copy() if task_data else {}
  1547. # 对于新任命类型,需要从数据库中查询实际的记录ID
  1548. if task_type == '新任命':
  1549. try:
  1550. from app.core.data_parse.parse_system import ParsedTalent
  1551. # 查询该任务相关的所有记录
  1552. parsed_records = ParsedTalent.query.filter_by(task_id=str(task_id), task_type=task_type).all()
  1553. record_ids = [str(record.id) for record in parsed_records]
  1554. parse_result = ','.join(record_ids) if record_ids else ''
  1555. except Exception as e:
  1556. logging.error(f"查询新任命记录ID失败: {str(e)}")
  1557. parse_result = ''
  1558. else:
  1559. parse_result = ','.join(parsed_record_ids) if parsed_record_ids else ''
  1560. return_data.update({
  1561. 'task_status': task_status,
  1562. 'parse_count': success_count,
  1563. 'parse_result': parse_result,
  1564. 'updated_at': get_east_asia_isoformat(),
  1565. 'updated_by': 'admin'
  1566. })
  1567. # 确定HTTP状态码
  1568. if failed_count == 0:
  1569. status_code = 200 # 完全成功
  1570. elif success_count > 0:
  1571. status_code = 206 # 部分成功
  1572. else:
  1573. status_code = 500 # 完全失败
  1574. return jsonify({
  1575. 'success': True,
  1576. 'message': result.get('message', '解析完成'),
  1577. 'data': return_data
  1578. }), status_code
  1579. else:
  1580. logging.error(f"执行{task_type}解析任务失败: {result.get('message', '')}")
  1581. # 设置任务状态为不成功
  1582. if task_obj:
  1583. task_obj.task_status = '不成功'
  1584. task_obj.parse_count = 0
  1585. task_obj.parse_result = ''
  1586. task_obj.updated_at = get_east_asia_time_naive()
  1587. task_obj.updated_by = 'admin'
  1588. db.session.commit()
  1589. logging.info(f"已更新解析任务记录: id={getattr(task_obj, 'id', None)}, 状态=不成功")
  1590. # 构建返回数据,按照请求参数格式返回
  1591. return_data = task_data.copy() if task_data else {}
  1592. return_data.update({
  1593. 'task_status': '不成功',
  1594. 'parse_count': 0,
  1595. 'parse_result': '',
  1596. 'updated_at': get_east_asia_isoformat(),
  1597. 'updated_by': 'admin'
  1598. })
  1599. return jsonify({
  1600. 'success': False,
  1601. 'message': result.get('message', '解析失败'),
  1602. 'data': return_data
  1603. }), 500
  1604. except Exception as process_error:
  1605. error_msg = f"执行{task_type}解析任务时发生错误: {str(process_error)}"
  1606. logging.error(error_msg, exc_info=True)
  1607. return jsonify({
  1608. 'success': False,
  1609. 'message': error_msg,
  1610. 'data': None
  1611. }), 500
  1612. except Exception as e:
  1613. # 记录错误日志
  1614. error_msg = f"执行解析任务接口失败: {str(e)}"
  1615. logging.error(error_msg, exc_info=True)
  1616. # 返回错误响应
  1617. return jsonify({
  1618. 'success': False,
  1619. 'message': error_msg,
  1620. 'data': None
  1621. }), 500
  1622. @bp.route('/add-parsed-talents', methods=['POST'])
  1623. def add_parsed_talents_route():
  1624. """
  1625. 处理解析任务响应数据并写入人才信息接口
  1626. 请求参数:
  1627. - 请求体: 包含任务ID和人才数据的JSON对象 (JSON格式)
  1628. - task_id: 任务ID,用于更新任务状态(可选)
  1629. - task_type: 任务类型(可选)
  1630. - data: 包含人才解析结果的数据对象
  1631. 请求体格式(严格按照样例格式):
  1632. {
  1633. "task_id": "119",
  1634. "task_type": "名片",
  1635. "data": {
  1636. "results": [
  1637. {
  1638. "name_zh": "王仁",
  1639. "name_en": "Owen Wang",
  1640. "title_zh": "总经理",
  1641. "title_en": "General Manager",
  1642. "mobile": "+86 138 1685 0647",
  1643. "phone": null,
  1644. "email": "rwang5@urcove-hotels.com",
  1645. "hotel_zh": "上海静安逸扉酒店",
  1646. "hotel_en": "UrCove by HYATT Shanghai Jing'an",
  1647. "brand_zh": null,
  1648. "brand_en": null,
  1649. "affiliation_zh": null,
  1650. "affiliation_en": null,
  1651. "brand_group": "UrCove, HYATT",
  1652. "address_zh": "中国上海市静安区武定西路1185号",
  1653. "address_en": "No.1185 West Wuding Road, Jing'an District",
  1654. "postal_code_zh": "200042",
  1655. "postal_code_en": "200042",
  1656. "birthday": null,
  1657. "residence": null,
  1658. "age": 0,
  1659. "native_place": null,
  1660. "image_path": "",
  1661. "talent_profile": "测试用名片",
  1662. "career_path": [
  1663. {
  1664. "date": "2025-08-01",
  1665. "hotel_en": "UrCove by HYATT Shanghai Jing'an",
  1666. "hotel_zh": "上海静安逸扉酒店",
  1667. "image_path": "",
  1668. "source": "business_card_creation",
  1669. "title_en": "General Manager",
  1670. "title_zh": "总经理"
  1671. }
  1672. ],
  1673. "origin_source": [
  1674. {
  1675. "task_type": "招聘",
  1676. "minio_path": "http://example.com/path/to/image.jpg",
  1677. "source_date": "2025-08-01"
  1678. }
  1679. ],
  1680. "minio_path": "http://example.com/path/to/image.jpg" // 可选字段
  1681. }
  1682. ]
  1683. }
  1684. }
  1685. 返回:
  1686. - JSON: 包含批量处理结果和状态信息
  1687. 功能说明:
  1688. - 接收包含人才数据的请求体
  1689. - 严格按照样例格式处理 results 数组中的人才数据
  1690. - 调用 add_single_talent 函数将人才信息写入 business_cards 表
  1691. - 提供详细的处理统计和结果追踪
  1692. 状态码:
  1693. - 200: 全部处理成功
  1694. - 206: 部分处理成功
  1695. - 400: 请求参数错误
  1696. - 500: 服务器内部错误
  1697. """
  1698. try:
  1699. # 检查请求是否为 JSON 格式
  1700. if not request.is_json:
  1701. return jsonify({
  1702. 'success': False,
  1703. 'message': '请求必须是 JSON 格式'
  1704. }), 400
  1705. # 获取请求数据
  1706. api_response_data = request.get_json()
  1707. # 基本参数验证
  1708. if not api_response_data:
  1709. return jsonify({
  1710. 'success': False,
  1711. 'message': '请求数据不能为空'
  1712. }), 400
  1713. # 验证数据格式
  1714. if not isinstance(api_response_data, dict):
  1715. return jsonify({
  1716. 'success': False,
  1717. 'message': '请求数据必须是JSON对象格式'
  1718. }), 400
  1719. # 记录请求日志
  1720. total_results = 0
  1721. if api_response_data.get('data') and api_response_data['data'].get('results'):
  1722. total_results = len(api_response_data['data']['results'])
  1723. logger.info(f"收到处理人才数据请求,包含 {total_results} 条结果记录")
  1724. # 调用核心业务逻辑
  1725. result = add_parsed_talents(api_response_data)
  1726. # 根据处理结果设置HTTP状态码
  1727. if result.get('success', False):
  1728. if result.get('code') == 200:
  1729. status_code = 200 # 全部成功
  1730. elif result.get('code') == 206:
  1731. status_code = 206 # 部分成功
  1732. else:
  1733. status_code = 200 # 默认成功
  1734. else:
  1735. if result.get('code') == 400:
  1736. status_code = 400 # 参数错误
  1737. else:
  1738. status_code = 500 # 服务器错误
  1739. # 记录处理结果日志
  1740. if result.get('success'):
  1741. data_summary = result.get('data', {}).get('summary', {})
  1742. success_count = data_summary.get('success_count', 0)
  1743. failed_count = data_summary.get('failed_count', 0)
  1744. logger.info(f"处理人才数据完成: 成功 {success_count} 条,失败 {failed_count} 条")
  1745. else:
  1746. logger.error(f"处理人才数据失败: {result.get('message', '未知错误')}")
  1747. # 返回结果
  1748. return jsonify({
  1749. 'success': result.get('success', False),
  1750. 'message': result.get('message', '处理完成')
  1751. }), status_code
  1752. except Exception as e:
  1753. # 记录错误日志
  1754. error_msg = f"处理人才数据接口失败: {str(e)}"
  1755. logger.error(error_msg, exc_info=True)
  1756. # 返回错误响应
  1757. return jsonify({
  1758. 'success': False,
  1759. 'message': error_msg
  1760. }), 500
  1761. @bp.route('/get-parsed-talents', methods=['GET'])
  1762. def get_parsed_talents_route():
  1763. """
  1764. 获取解析人才记录列表接口
  1765. 请求参数:
  1766. - status (str, optional): 状态过滤参数,如果为空则查询所有记录
  1767. 请求示例:
  1768. GET /get-parsed-talents?status=待审核
  1769. GET /get-parsed-talents (查询所有记录)
  1770. 返回:
  1771. - JSON: 包含人才记录列表和状态信息
  1772. - 200: 成功获取数据
  1773. - 500: 服务器内部错误
  1774. """
  1775. try:
  1776. # 获取查询参数
  1777. status = request.args.get('status', '').strip()
  1778. # 调用核心业务逻辑
  1779. from app.core.data_parse.parse_system import get_parsed_talents
  1780. result = get_parsed_talents(status)
  1781. # 根据处理结果设置HTTP状态码
  1782. if result.get('success', False):
  1783. status_code = result.get('code', 200)
  1784. else:
  1785. status_code = result.get('code', 500)
  1786. # 记录处理结果日志
  1787. if result.get('success'):
  1788. count = result.get('count', 0)
  1789. if status:
  1790. logging.info(f"成功获取状态为 '{status}' 的解析人才记录: {count} 条")
  1791. else:
  1792. logging.info(f"成功获取所有解析人才记录: {count} 条")
  1793. else:
  1794. logging.error(f"获取解析人才记录失败: {result.get('message', '未知错误')}")
  1795. # 返回结果
  1796. return jsonify({
  1797. 'success': result.get('success', False),
  1798. 'message': result.get('message', '处理完成'),
  1799. 'data': result.get('data', []),
  1800. 'count': result.get('count', 0)
  1801. }), status_code
  1802. except Exception as e:
  1803. # 记录错误日志
  1804. error_msg = f"获取解析人才记录接口失败: {str(e)}"
  1805. logging.error(error_msg, exc_info=True)
  1806. # 返回错误响应
  1807. return jsonify({
  1808. 'success': False,
  1809. 'message': error_msg,
  1810. 'data': [],
  1811. 'count': 0
  1812. }), 500
  1813. @bp.route('/process-urls', methods=['POST'])
  1814. def process_urls_route():
  1815. """
  1816. 处理网页URL爬取接口
  1817. 请求参数:
  1818. - JSON格式,包含以下字段:
  1819. - urlArr: 字符串数组,每个元素为一个网页URL地址
  1820. 请求示例:
  1821. POST /process-urls
  1822. Content-Type: application/json
  1823. {
  1824. "urlArr": [
  1825. "https://example.com/page1",
  1826. "https://example.com/page2",
  1827. "https://mp.weixin.qq.com/s/4yz-kNAWAlF36aeQ_cgQQg"
  1828. ]
  1829. }
  1830. 返回:
  1831. - JSON: 包含网页爬取结果的字典
  1832. 返回格式:
  1833. {
  1834. "success": true/false,
  1835. "message": "处理结果描述",
  1836. "data": {
  1837. "total_urls": 总URL数量,
  1838. "success_count": 成功爬取的URL数量,
  1839. "failed_count": 失败的URL数量,
  1840. "contents": [
  1841. {
  1842. "url": "URL地址",
  1843. "data": "网页内容",
  1844. "status": "success",
  1845. "content_length": 内容长度,
  1846. "original_length": 原始内容长度,
  1847. "status_code": HTTP状态码,
  1848. "encoding": 编码格式
  1849. }
  1850. ],
  1851. "failed_items": [
  1852. {
  1853. "url": "URL地址",
  1854. "error": "错误信息",
  1855. "status": "failed"
  1856. }
  1857. ]
  1858. }
  1859. }
  1860. 功能说明:
  1861. - 接收包含URL数组的POST请求
  1862. - 调用web_url_crawl函数进行网页内容爬取
  1863. - 返回结构化的爬取结果
  1864. - 支持批量处理多个URL
  1865. - 提供详细的成功/失败统计信息
  1866. 状态码:
  1867. - 200: 完全成功,所有URL都成功爬取
  1868. - 206: 部分成功,部分URL成功爬取
  1869. - 400: 请求参数错误
  1870. - 500: 服务器内部错误
  1871. """
  1872. try:
  1873. # 检查请求是否为JSON格式
  1874. if not request.is_json:
  1875. return jsonify({
  1876. 'success': False,
  1877. 'message': '请求必须是JSON格式'
  1878. }), 400
  1879. # 获取请求数据
  1880. request_data = request.get_json()
  1881. # 基本参数验证
  1882. if not request_data:
  1883. return jsonify({
  1884. 'success': False,
  1885. 'message': '请求数据不能为空'
  1886. }), 400
  1887. # 验证urlArr字段
  1888. if 'urlArr' not in request_data:
  1889. return jsonify({
  1890. 'success': False,
  1891. 'message': '缺少必填字段: urlArr'
  1892. }), 400
  1893. url_arr = request_data.get('urlArr')
  1894. # 验证urlArr是否为数组
  1895. if not isinstance(url_arr, list):
  1896. return jsonify({
  1897. 'success': False,
  1898. 'message': 'urlArr字段必须是数组格式'
  1899. }), 400
  1900. # 验证urlArr是否为空
  1901. if len(url_arr) == 0:
  1902. return jsonify({
  1903. 'success': False,
  1904. 'message': 'urlArr数组不能为空'
  1905. }), 400
  1906. # 验证每个URL是否为字符串
  1907. for i, url in enumerate(url_arr):
  1908. if not isinstance(url, str):
  1909. return jsonify({
  1910. 'success': False,
  1911. 'message': f'urlArr[{i}]必须是字符串格式,当前类型: {type(url).__name__}'
  1912. }), 400
  1913. # 记录请求日志
  1914. logger.info(f"收到网页URL爬取请求,包含 {len(url_arr)} 个URL")
  1915. # 调用核心业务逻辑 - web_url_crawl函数
  1916. result = web_url_crawl(url_arr)
  1917. # 根据处理结果设置HTTP状态码
  1918. if result.get('success', False):
  1919. success_count = result.get('data', {}).get('success_count', 0)
  1920. failed_count = result.get('data', {}).get('failed_count', 0)
  1921. if failed_count == 0:
  1922. status_code = 200 # 完全成功
  1923. elif success_count > 0:
  1924. status_code = 206 # 部分成功
  1925. else:
  1926. status_code = 500 # 完全失败
  1927. else:
  1928. status_code = 500 # 服务器错误
  1929. # 记录处理结果日志
  1930. if result.get('success'):
  1931. data = result.get('data', {})
  1932. success_count = data.get('success_count', 0)
  1933. failed_count = data.get('failed_count', 0)
  1934. total_urls = data.get('total_urls', 0)
  1935. if failed_count == 0:
  1936. logger.info(f"网页URL爬取完全成功: 共 {total_urls} 个URL,全部成功")
  1937. else:
  1938. logger.info(f"网页URL爬取部分成功: 共 {total_urls} 个URL,成功 {success_count} 个,失败 {failed_count} 个")
  1939. else:
  1940. logger.error(f"网页URL爬取失败: {result.get('message', '未知错误')}")
  1941. # 返回结果
  1942. return jsonify({
  1943. 'success': result.get('success', False),
  1944. 'message': result.get('message', '处理完成'),
  1945. 'data': result.get('data', {})
  1946. }), status_code
  1947. except Exception as e:
  1948. # 记录错误日志
  1949. error_msg = f"网页URL爬取接口失败: {str(e)}"
  1950. logger.error(error_msg, exc_info=True)
  1951. # 返回错误响应
  1952. return jsonify({
  1953. 'success': False,
  1954. 'message': error_msg,
  1955. 'data': {
  1956. 'total_urls': 0,
  1957. 'success_count': 0,
  1958. 'failed_count': 0,
  1959. 'contents': [],
  1960. 'failed_items': []
  1961. }
  1962. }), 500
  1963. @bp.route('/get-calendar-info', methods=['GET'])
  1964. def get_calendar_info_api():
  1965. """
  1966. 获取指定日期的黄历信息
  1967. GET /api/data_parse/get-calendar-info?date=YYYY-MM-DD
  1968. Args:
  1969. date (str): 查询日期,格式为YYYY-MM-DD
  1970. Returns:
  1971. JSON: 包含黄历信息的响应数据
  1972. """
  1973. try:
  1974. # 获取查询参数
  1975. date_param = request.args.get('date')
  1976. # 验证日期参数
  1977. if not date_param:
  1978. return jsonify({
  1979. 'reason': 'failed',
  1980. 'return_code': 400,
  1981. 'result': None,
  1982. 'error': '缺少必填参数: date'
  1983. }), 400
  1984. # 验证日期格式
  1985. if not isinstance(date_param, str) or len(date_param) != 10:
  1986. return jsonify({
  1987. 'reason': 'failed',
  1988. 'return_code': 400,
  1989. 'result': None,
  1990. 'error': '日期格式错误,请使用YYYY-MM-DD格式'
  1991. }), 400
  1992. # 记录请求日志
  1993. logger.info(f"收到黄历信息查询请求,日期: {date_param}")
  1994. # 调用核心业务逻辑 - get_calendar_by_date函数
  1995. result = get_calendar_by_date(date_param)
  1996. # 根据返回结果设置HTTP状态码
  1997. status_code = result.get('return_code', 500)
  1998. # 记录处理结果日志
  1999. if result.get('return_code') == 200:
  2000. logger.info(f"黄历信息查询成功,日期: {date_param}")
  2001. else:
  2002. error_msg = result.get('error', '未知错误')
  2003. logger.warning(f"黄历信息查询失败,日期: {date_param},错误: {error_msg}")
  2004. # 返回结果
  2005. return jsonify(result), status_code
  2006. except Exception as e:
  2007. # 记录错误日志
  2008. error_msg = f"黄历信息查询接口失败: {str(e)}"
  2009. logger.error(error_msg, exc_info=True)
  2010. # 返回错误响应
  2011. return jsonify({
  2012. 'reason': 'failed',
  2013. 'return_code': 500,
  2014. 'result': None,
  2015. 'error': error_msg
  2016. }), 500
  2017. # ================================
  2018. # 微信认证相关API路由
  2019. # ================================
  2020. @bp.route('/wechat-register', methods=['POST'])
  2021. def wechat_register_api():
  2022. """
  2023. 微信用户注册接口
  2024. POST /api/parse/wechat-register
  2025. Request Body:
  2026. {
  2027. "wechat_code": "wx_code_12345", // 必填:微信授权码(15分钟有效期)
  2028. "phone_number": "13800138000", // 可选:手机号码
  2029. "id_card_number": "110101199001011234", // 可选:身份证号码
  2030. "platform": "miniprogram" // 可选:微信平台类型,默认为小程序
  2031. }
  2032. Returns:
  2033. JSON: 包含注册结果的响应数据,成功时返回用户openid等信息
  2034. """
  2035. try:
  2036. # 获取请求数据
  2037. data = request.get_json()
  2038. # 验证请求数据
  2039. if not data:
  2040. return jsonify({
  2041. 'reason': 'failed',
  2042. 'return_code': 400,
  2043. 'result': None,
  2044. 'error': '请求体不能为空'
  2045. }), 400
  2046. # 验证必填参数
  2047. wechat_code = data.get('wechat_code')
  2048. if not wechat_code:
  2049. return jsonify({
  2050. 'reason': 'failed',
  2051. 'return_code': 400,
  2052. 'result': None,
  2053. 'error': '缺少必填参数: wechat_code'
  2054. }), 400
  2055. # 获取可选参数
  2056. phone_number = data.get('phone_number')
  2057. id_card_number = data.get('id_card_number')
  2058. platform = data.get('platform', 'miniprogram')
  2059. # 记录请求日志
  2060. logger.info(f"收到微信用户注册请求,wechat_code: {wechat_code}, platform: {platform}")
  2061. # 调用核心业务逻辑
  2062. result = register_wechat_user(wechat_code, phone_number, id_card_number, platform)
  2063. # 根据返回结果设置HTTP状态码
  2064. status_code = result.get('return_code', 500)
  2065. # 记录处理结果日志
  2066. if result.get('return_code') == 201:
  2067. logger.info(f"微信用户注册成功,wechat_code: {wechat_code}")
  2068. else:
  2069. error_msg = result.get('error', '未知错误')
  2070. logger.warning(f"微信用户注册失败,wechat_code: {wechat_code},错误: {error_msg}")
  2071. # 返回结果
  2072. return jsonify(result), status_code
  2073. except Exception as e:
  2074. # 记录错误日志
  2075. error_msg = f"微信用户注册接口失败: {str(e)}"
  2076. logger.error(error_msg, exc_info=True)
  2077. # 返回错误响应
  2078. return jsonify({
  2079. 'reason': 'failed',
  2080. 'return_code': 500,
  2081. 'result': None,
  2082. 'error': error_msg
  2083. }), 500
  2084. @bp.route('/wechat-login', methods=['POST'])
  2085. def wechat_login_api():
  2086. """
  2087. 微信用户登录接口
  2088. POST /api/parse/wechat-login
  2089. Request Body:
  2090. {
  2091. "wechat_code": "wx_code_12345", // 必填:微信授权码(15分钟有效期)
  2092. "platform": "miniprogram" // 可选:微信平台类型,默认为小程序
  2093. }
  2094. Returns:
  2095. JSON: 包含登录结果的响应数据,成功时返回用户openid等信息
  2096. """
  2097. try:
  2098. # 获取请求数据
  2099. data = request.get_json()
  2100. # 验证请求数据
  2101. if not data:
  2102. return jsonify({
  2103. 'reason': 'failed',
  2104. 'return_code': 400,
  2105. 'result': None,
  2106. 'error': '请求体不能为空'
  2107. }), 400
  2108. # 验证必填参数
  2109. wechat_code = data.get('wechat_code')
  2110. if not wechat_code:
  2111. return jsonify({
  2112. 'reason': 'failed',
  2113. 'return_code': 400,
  2114. 'result': None,
  2115. 'error': '缺少必填参数: wechat_code'
  2116. }), 400
  2117. # 获取可选参数
  2118. platform = data.get('platform', 'miniprogram')
  2119. # 记录请求日志
  2120. logger.info(f"收到微信用户登录请求,wechat_code: {wechat_code}, platform: {platform}")
  2121. # 调用核心业务逻辑
  2122. result = login_wechat_user(wechat_code, platform)
  2123. # 根据返回结果设置HTTP状态码
  2124. status_code = result.get('return_code', 500)
  2125. # 记录处理结果日志
  2126. if result.get('return_code') == 200:
  2127. logger.info(f"微信用户登录成功,wechat_code: {wechat_code}")
  2128. else:
  2129. error_msg = result.get('error', '未知错误')
  2130. logger.warning(f"微信用户登录失败,wechat_code: {wechat_code},错误: {error_msg}")
  2131. # 返回结果
  2132. return jsonify(result), status_code
  2133. except Exception as e:
  2134. # 记录错误日志
  2135. error_msg = f"微信用户登录接口失败: {str(e)}"
  2136. logger.error(error_msg, exc_info=True)
  2137. # 返回错误响应
  2138. return jsonify({
  2139. 'reason': 'failed',
  2140. 'return_code': 500,
  2141. 'result': None,
  2142. 'error': error_msg
  2143. }), 500
  2144. @bp.route('/wechat-logout', methods=['POST'])
  2145. def wechat_logout_api():
  2146. """
  2147. 微信用户登出接口
  2148. POST /api/parse/wechat-logout
  2149. Request Body:
  2150. {
  2151. "openid": "wx_openid_abcd1234567890" // 必填:微信用户openid
  2152. }
  2153. Returns:
  2154. JSON: 包含登出结果的响应数据
  2155. """
  2156. try:
  2157. # 获取请求数据
  2158. data = request.get_json()
  2159. # 验证请求数据
  2160. if not data:
  2161. return jsonify({
  2162. 'reason': 'failed',
  2163. 'return_code': 400,
  2164. 'result': None,
  2165. 'error': '请求体不能为空'
  2166. }), 400
  2167. # 验证必填参数
  2168. openid = data.get('openid')
  2169. if not openid:
  2170. return jsonify({
  2171. 'reason': 'failed',
  2172. 'return_code': 400,
  2173. 'result': None,
  2174. 'error': '缺少必填参数: openid'
  2175. }), 400
  2176. # 记录请求日志
  2177. logger.info(f"收到微信用户登出请求,openid: {openid}")
  2178. # 调用核心业务逻辑
  2179. result = logout_wechat_user(openid)
  2180. # 根据返回结果设置HTTP状态码
  2181. status_code = result.get('return_code', 500)
  2182. # 记录处理结果日志
  2183. if result.get('return_code') == 200:
  2184. logger.info(f"微信用户登出成功,openid: {openid}")
  2185. else:
  2186. error_msg = result.get('error', '未知错误')
  2187. logger.warning(f"微信用户登出失败,openid: {openid},错误: {error_msg}")
  2188. # 返回结果
  2189. return jsonify(result), status_code
  2190. except Exception as e:
  2191. # 记录错误日志
  2192. error_msg = f"微信用户登出接口失败: {str(e)}"
  2193. logger.error(error_msg, exc_info=True)
  2194. # 返回错误响应
  2195. return jsonify({
  2196. 'reason': 'failed',
  2197. 'return_code': 500,
  2198. 'result': None,
  2199. 'error': error_msg
  2200. }), 500
  2201. @bp.route('/wechat-user', methods=['GET'])
  2202. def wechat_get_user_info_api():
  2203. """
  2204. 获取微信用户信息接口
  2205. GET /api/parse/wechat-user?openid=wx_openid_abcd1234567890
  2206. Args:
  2207. openid (str): 微信用户openid,作为查询参数
  2208. Returns:
  2209. JSON: 包含用户信息的响应数据
  2210. """
  2211. try:
  2212. # 获取查询参数
  2213. openid = request.args.get('openid')
  2214. # 验证必填参数
  2215. if not openid:
  2216. return jsonify({
  2217. 'reason': 'failed',
  2218. 'return_code': 400,
  2219. 'result': None,
  2220. 'error': '缺少必填参数: openid'
  2221. }), 400
  2222. # 记录请求日志
  2223. logger.info(f"收到获取微信用户信息请求,openid: {openid}")
  2224. # 调用核心业务逻辑
  2225. result = get_wechat_user_info(openid)
  2226. # 根据返回结果设置HTTP状态码
  2227. status_code = result.get('return_code', 500)
  2228. # 记录处理结果日志
  2229. if result.get('return_code') == 200:
  2230. logger.info(f"获取微信用户信息成功,openid: {openid}")
  2231. else:
  2232. error_msg = result.get('error', '未知错误')
  2233. logger.warning(f"获取微信用户信息失败,openid: {openid},错误: {error_msg}")
  2234. # 返回结果
  2235. return jsonify(result), status_code
  2236. except Exception as e:
  2237. # 记录错误日志
  2238. error_msg = f"获取微信用户信息接口失败: {str(e)}"
  2239. logger.error(error_msg, exc_info=True)
  2240. # 返回错误响应
  2241. return jsonify({
  2242. 'reason': 'failed',
  2243. 'return_code': 500,
  2244. 'result': None,
  2245. 'error': error_msg
  2246. }), 500
  2247. @bp.route('/wechat-user', methods=['PUT'])
  2248. def wechat_update_user_info_api():
  2249. """
  2250. 更新微信用户信息接口
  2251. PUT /api/parse/wechat-user
  2252. Request Body:
  2253. {
  2254. "openid": "wx_openid_abcd1234567890", // 必填:微信用户openid
  2255. "phone_number": "13900139000", // 可选:要更新的手机号码
  2256. "id_card_number": "110101199001011234" // 可选:要更新的身份证号码
  2257. }
  2258. Returns:
  2259. JSON: 包含更新结果的响应数据
  2260. """
  2261. try:
  2262. # 获取请求数据
  2263. data = request.get_json()
  2264. # 验证请求数据
  2265. if not data:
  2266. return jsonify({
  2267. 'reason': 'failed',
  2268. 'return_code': 400,
  2269. 'result': None,
  2270. 'error': '请求体不能为空'
  2271. }), 400
  2272. # 验证必填参数
  2273. openid = data.get('openid')
  2274. if not openid:
  2275. return jsonify({
  2276. 'reason': 'failed',
  2277. 'return_code': 400,
  2278. 'result': None,
  2279. 'error': '缺少必填参数: openid'
  2280. }), 400
  2281. # 构建更新数据,排除openid
  2282. update_data = {}
  2283. if 'phone_number' in data:
  2284. update_data['phone_number'] = data['phone_number']
  2285. if 'id_card_number' in data:
  2286. update_data['id_card_number'] = data['id_card_number']
  2287. # 检查是否有要更新的数据
  2288. if not update_data:
  2289. return jsonify({
  2290. 'reason': 'failed',
  2291. 'return_code': 400,
  2292. 'result': None,
  2293. 'error': '没有提供要更新的数据'
  2294. }), 400
  2295. # 记录请求日志
  2296. logger.info(f"收到更新微信用户信息请求,openid: {openid}, 更新字段: {list(update_data.keys())}")
  2297. # 调用核心业务逻辑
  2298. result = update_wechat_user_info(openid, update_data)
  2299. # 根据返回结果设置HTTP状态码
  2300. status_code = result.get('return_code', 500)
  2301. # 记录处理结果日志
  2302. if result.get('return_code') == 200:
  2303. logger.info(f"更新微信用户信息成功,openid: {openid}")
  2304. else:
  2305. error_msg = result.get('error', '未知错误')
  2306. logger.warning(f"更新微信用户信息失败,openid: {openid},错误: {error_msg}")
  2307. # 返回结果
  2308. return jsonify(result), status_code
  2309. except Exception as e:
  2310. # 记录错误日志
  2311. error_msg = f"更新微信用户信息接口失败: {str(e)}"
  2312. logger.error(error_msg, exc_info=True)
  2313. # 返回错误响应
  2314. return jsonify({
  2315. 'reason': 'failed',
  2316. 'return_code': 500,
  2317. 'result': None,
  2318. 'error': error_msg
  2319. }), 500
  2320. # ================================
  2321. # 日历记录相关API路由
  2322. # ================================
  2323. @bp.route('/save-calendar-record', methods=['POST'])
  2324. def save_calendar_record_api():
  2325. """
  2326. 保存日历记录接口
  2327. POST /api/parse/save-calendar-record
  2328. Request Body:
  2329. {
  2330. "openid": "wx_openid_abcd1234567890123456", // 必填:微信用户openid
  2331. "month_key": "2024-01", // 必填:月份标识(YYYY-MM格式)
  2332. "calendar_content": [ // 必填:日历内容(JSON数组)
  2333. {
  2334. "date": "2024-01-01",
  2335. "events": ["元旦节"],
  2336. "notes": "新年快乐"
  2337. },
  2338. {
  2339. "date": "2024-01-15",
  2340. "events": ["会议", "约会"],
  2341. "notes": "重要日程"
  2342. }
  2343. ]
  2344. }
  2345. Returns:
  2346. JSON: 包含保存结果的响应数据
  2347. """
  2348. try:
  2349. # 获取请求数据
  2350. data = request.get_json()
  2351. # 验证请求数据
  2352. if not data:
  2353. return jsonify({
  2354. 'reason': 'failed',
  2355. 'return_code': 400,
  2356. 'result': None,
  2357. 'error': '请求体不能为空'
  2358. }), 400
  2359. # 验证必填参数
  2360. openid = data.get('openid')
  2361. month_key = data.get('month_key')
  2362. calendar_content = data.get('calendar_content')
  2363. if not openid:
  2364. return jsonify({
  2365. 'reason': 'failed',
  2366. 'return_code': 400,
  2367. 'result': None,
  2368. 'error': '缺少必填参数: openid'
  2369. }), 400
  2370. if not month_key:
  2371. return jsonify({
  2372. 'reason': 'failed',
  2373. 'return_code': 400,
  2374. 'result': None,
  2375. 'error': '缺少必填参数: month_key'
  2376. }), 400
  2377. if calendar_content is None:
  2378. return jsonify({
  2379. 'reason': 'failed',
  2380. 'return_code': 400,
  2381. 'result': None,
  2382. 'error': '缺少必填参数: calendar_content'
  2383. }), 400
  2384. # 记录请求日志
  2385. logger.info(f"收到保存日历记录请求,openid: {openid}, month_key: {month_key}")
  2386. # 调用核心业务逻辑
  2387. result = save_calendar_record(data)
  2388. # 根据返回结果设置HTTP状态码
  2389. status_code = result.get('return_code', 500)
  2390. # 记录处理结果日志
  2391. if result.get('return_code') == 200:
  2392. logger.info(f"保存日历记录成功,openid: {openid}, month_key: {month_key}")
  2393. else:
  2394. error_msg = result.get('error', '未知错误')
  2395. logger.warning(f"保存日历记录失败,openid: {openid}, month_key: {month_key},错误: {error_msg}")
  2396. # 返回结果
  2397. return jsonify(result), status_code
  2398. except Exception as e:
  2399. # 记录错误日志
  2400. error_msg = f"保存日历记录接口失败: {str(e)}"
  2401. logger.error(error_msg, exc_info=True)
  2402. # 返回错误响应
  2403. return jsonify({
  2404. 'reason': 'failed',
  2405. 'return_code': 500,
  2406. 'result': None,
  2407. 'error': error_msg
  2408. }), 500
  2409. @bp.route('/get-calendar-record', methods=['GET'])
  2410. def get_calendar_record_api():
  2411. """
  2412. 获取日历记录接口
  2413. GET /api/parse/get-calendar-record?openid=wx_openid_abcd1234567890123456&month_key=2024-01
  2414. Args:
  2415. openid (str): 微信用户openid,作为查询参数
  2416. month_key (str): 月份标识(YYYY-MM格式),作为查询参数
  2417. Returns:
  2418. JSON: 包含查询结果的响应数据
  2419. """
  2420. try:
  2421. # 获取查询参数
  2422. openid = request.args.get('openid')
  2423. month_key = request.args.get('month_key')
  2424. # 验证必填参数
  2425. if not openid:
  2426. return jsonify({
  2427. 'reason': 'failed',
  2428. 'return_code': 400,
  2429. 'result': None,
  2430. 'error': '缺少必填参数: openid'
  2431. }), 400
  2432. if not month_key:
  2433. return jsonify({
  2434. 'reason': 'failed',
  2435. 'return_code': 400,
  2436. 'result': None,
  2437. 'error': '缺少必填参数: month_key'
  2438. }), 400
  2439. # 记录请求日志
  2440. logger.info(f"收到获取日历记录请求,openid: {openid}, month_key: {month_key}")
  2441. # 调用核心业务逻辑
  2442. result = get_calendar_record(openid, month_key)
  2443. # 根据返回结果设置HTTP状态码
  2444. status_code = result.get('return_code', 500)
  2445. # 记录处理结果日志
  2446. if result.get('return_code') == 200:
  2447. has_content = result.get('result', {}).get('id') is not None
  2448. logger.info(f"获取日历记录成功,openid: {openid}, month_key: {month_key}, 有记录: {has_content}")
  2449. else:
  2450. error_msg = result.get('error', '未知错误')
  2451. logger.warning(f"获取日历记录失败,openid: {openid}, month_key: {month_key},错误: {error_msg}")
  2452. # 返回结果
  2453. return jsonify(result), status_code
  2454. except Exception as e:
  2455. # 记录错误日志
  2456. error_msg = f"获取日历记录接口失败: {str(e)}"
  2457. logger.error(error_msg, exc_info=True)
  2458. # 返回错误响应
  2459. return jsonify({
  2460. 'reason': 'failed',
  2461. 'return_code': 500,
  2462. 'result': None,
  2463. 'error': error_msg
  2464. }), 500