api_app.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. import datetime
  2. import logging
  3. import os
  4. import socket
  5. import subprocess
  6. import requests
  7. from flask import Flask, jsonify, request, current_app, send_from_directory, Response
  8. from flask_cors import CORS
  9. from requests_toolbelt import MultipartEncoder
  10. from config import file_type_map, TARGET_URL
  11. from tools.file_manager import get_file_md5
  12. from tools.logger_handle import logger
  13. socket.getfqdn = lambda name=None: 'localhost'
  14. app = Flask(__name__, static_folder='../static')
  15. CORS(app, resources=r'/*')
  16. class InterceptHandler(logging.Handler):
  17. def emit(self, record):
  18. # 将标准日志记录转为 loguru
  19. try:
  20. level = logger.level(record.levelname).name
  21. except ValueError:
  22. level = record.levelno
  23. logger.opt(depth=6, exception=record.exc_info).log(level, record.getMessage())
  24. logging.basicConfig(handlers=[InterceptHandler()], level=logging.INFO)
  25. for name in ("flask", "werkzeug", "watchdog"):
  26. logging.getLogger(name).handlers = [InterceptHandler()]
  27. logging.getLogger(name).propagate = False
  28. @app.route('/')
  29. def index():
  30. return send_from_directory(app.static_folder, 'index.html')
  31. # 代理静态文件(如 css, js, images 等)
  32. @app.route('/<path:path>')
  33. def static_proxy(path):
  34. return send_from_directory(app.static_folder, path)
  35. @logger.catch()
  36. @app.route('/pyapi/<path:path>', methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH'])
  37. def proxy(path):
  38. url = f'{TARGET_URL}/{path}'
  39. logger.info(f'接收到请求{url}')
  40. raw_body = request.get_data(cache=True)
  41. # 获取 headers(排除 Host 避免冲突)
  42. headers = {key: value for key, value in request.headers if key.lower() != 'host'}
  43. headers[
  44. 'User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36'
  45. try:
  46. if request.files or (request.content_type and 'multipart/form-data' in request.content_type.lower()):
  47. # 构造 Multipart 表单
  48. fields = {}
  49. # 添加普通字段
  50. if request.files:
  51. for key, value in request.form.items():
  52. fields[key] = value
  53. # 添加文件字段
  54. for key, file in request.files.items():
  55. fields[key] = (file.filename, file.stream, file.mimetype)
  56. form_data = MultipartEncoder(fields=fields)
  57. headers['Content-Type'] = form_data.content_type
  58. else:
  59. form_data = raw_body
  60. resp = requests.request(
  61. method=request.method,
  62. url=url,
  63. headers=headers,
  64. params=request.args,
  65. data=form_data,
  66. cookies=request.cookies,
  67. allow_redirects=False
  68. )
  69. else:
  70. # JSON 或普通表单
  71. json_data = None
  72. form_data = None
  73. if request.content_type and 'application/json' in request.content_type.lower():
  74. json_data = request.get_json(silent=True)
  75. else:
  76. form_data = request.form.to_dict()
  77. resp = requests.request(
  78. method=request.method,
  79. url=url,
  80. headers=headers,
  81. params=request.args,
  82. data=form_data,
  83. json=json_data,
  84. cookies=request.cookies,
  85. allow_redirects=False
  86. )
  87. except Exception as e:
  88. logger.error(e)
  89. return jsonify({'code': -1})
  90. # 构建 Flask 的响应
  91. excluded_headers = ['content-encoding', 'content-length', 'transfer-encoding', 'connection']
  92. response_headers = [(k, v) for k, v in resp.raw.headers.items() if k.lower() not in excluded_headers]
  93. return Response(resp.content, resp.status_code, response_headers)
  94. @logger.catch()
  95. def open_file_by_wps(file_path):
  96. ext = file_path.split(".")[-1]
  97. if ext not in file_type_map:
  98. return jsonify({'code': 3001, 'msg': '不支持该类型的文件'})
  99. if not os.path.exists(file_path):
  100. return jsonify({'code': 3006, 'msg': '文件不存在'})
  101. subprocess.run([file_type_map[ext], file_path])
  102. return jsonify({'code': 1000, 'msg': '操作完成'})
  103. @logger.catch()
  104. @app.route('/download_and_open_file', methods=['POST'])
  105. def download_and_open_with_wps():
  106. '''
  107. 1000: 操作完成
  108. 2001: 检测到文档正在被编辑,无法下载
  109. 2002: 检测到远程文件已经更新本地文件也发生改动,终止操作
  110. 3001: 不支持该类型的文件
  111. 3002: 下载文件失败
  112. :return:
  113. '''
  114. file_id = request.get_json()['file_id']
  115. is_template = request.get_json()['is_template']
  116. # 判断是否为exe模式执行
  117. if is_template:
  118. file_info = app.config['serve_client'].get_template_file_info(file_id)
  119. time_str = datetime.datetime.now().strftime('%Y-%m-%dT%H-%M-%S')
  120. local_path = os.path.join(current_app.config['work_path'],
  121. f'{file_info["name"]}({time_str}).{file_info["nameSuffix"]}')
  122. if app.config['serve_client'].download_template(file_info['fileLink'], local_path):
  123. return open_file_by_wps(local_path)
  124. else:
  125. return jsonify({'code': 3006, 'msg': '文件不存在'})
  126. file_info = app.config['serve_client'].get_file_info(file_id)
  127. if not file_info:
  128. return jsonify({'code': 3006, 'msg': '文件不存在'})
  129. local_file = os.path.join(current_app.config['work_path'], file_info['filePath'].replace('/', '_'))
  130. # 判断本地是否存在已下载的同名文件
  131. if not os.path.exists(local_file) or not os.path.exists(local_file + '.metadata.json'):
  132. app.config['serve_client'].download_file(file_info,
  133. current_app.config['work_path'])
  134. return open_file_by_wps(local_file)
  135. # 判断本地文件和线上文件是否一致
  136. local_file_metadata = current_app.config['serve_client'].load_metadata(local_file)
  137. if local_file_metadata['update_time'] == file_info['updateTime']:
  138. return open_file_by_wps(local_file)
  139. if local_file_metadata['md5'] == app.config['serve_client'].get_file_md5(local_file):
  140. app.config['serve_client'].download_file(file_info, current_app.config['work_path'])
  141. return open_file_by_wps(local_file)
  142. return jsonify({'code': 2002, 'msg': '检测到远程文件已经更新本地文件也发生改动,终止操作'})
  143. @logger.catch()
  144. @app.route('/upload_local_file', methods=['POST'])
  145. def upload_local_file():
  146. file_name = request.get_json()['file_name']
  147. metadata = app.config['serve_client'].load_metadata(os.path.join(current_app.config['work_path'], file_name))
  148. code = app.config['serve_client'].upload_file(os.path.join(current_app.config['work_path'], file_name))
  149. if not code:
  150. return jsonify({'code': 3003, 'msg': '操作失败,上传文件失败'})
  151. try:
  152. os.remove(os.path.join(current_app.config['work_path'], file_name))
  153. os.remove(os.path.join(current_app.config['work_path'], file_name + '.metadata'))
  154. file_info = app.config['serve_client'].get_file_info(metadata['file_id'])
  155. if not file_info:
  156. return jsonify({'code': 3006, 'msg': '文件不存在'})
  157. app.config['serve_client'].download_file(file_info, current_app.config['work_path'])
  158. except Exception as e:
  159. logger.exception(e)
  160. return jsonify({'code': 1000, 'msg': '文件上传成功,本地文件清除失败'})
  161. return jsonify({'code': 1000, 'msg': '操作成功'})
  162. @logger.catch()
  163. @app.route('/update_local_file', methods=['POST'])
  164. def update_local_file():
  165. file_name = request.get_json()['file_name']
  166. try:
  167. metadata = current_app.config['serve_client'].load_metadata(os.path.join(current_app.config['work_path'], file_name))
  168. os.remove(os.path.join(current_app.config['work_path'], file_name))
  169. os.remove(os.path.join(current_app.config['work_path'], file_name + '.metadata'))
  170. except Exception as e:
  171. logger.exception(e)
  172. return jsonify({'code': 3005, 'msg': '操作失败,无权限操作本地文件'})
  173. try:
  174. file_info = app.config['serve_client'].get_file_info(metadata['file_id'])
  175. if not app.config['serve_client'].download_file(file_info, current_app.config['work_path']):
  176. return jsonify({'code': 3005, 'msg': '文件下载失败'})
  177. except Exception as e:
  178. logger.exception(e)
  179. return jsonify({'code': 3005, 'msg': '文件下载失败'})
  180. return jsonify({'code': 1000, 'msg': '操作成功'})
  181. @logger.catch()
  182. @app.route('/file_state_list', methods=['GET'])
  183. def file_state_list():
  184. '''
  185. state : 0 正常, 1: 云端有更新 2: 本地有变动且无冲突可以上传 3: 本地文件和云端文件有冲突
  186. :return:
  187. '''
  188. file_info_list = []
  189. for file_name in os.listdir(current_app.config['work_path']):
  190. suffer = file_name.split('.')[-1]
  191. if suffer not in file_type_map:
  192. continue
  193. if not os.path.exists(os.path.join(current_app.config['work_path'], file_name + '.metadata')):
  194. continue
  195. metadata = current_app.config['serve_client'].load_metadata(os.path.join(current_app.config['work_path'], file_name))
  196. file_info = {
  197. 'file_name': file_name,
  198. 'show_name': metadata['file_name'],
  199. 'cloud_update_time': metadata['update_time'],
  200. 'source_md5': metadata['md5'],
  201. 'state': 0
  202. }
  203. cloud_file_info = current_app.config['serve_client'].get_file_info(metadata['file_id'])
  204. if cloud_file_info == 2:
  205. file_info['state'] = 4
  206. file_info_list.append(file_info)
  207. continue
  208. if not cloud_file_info:
  209. file_info_list.append(file_info)
  210. continue
  211. if cloud_file_info['updateTime'] != metadata['update_time']:
  212. file_info['state'] = 1
  213. try:
  214. file_md5 = get_file_md5(os.path.join(current_app.config['work_path'], file_name))
  215. if file_md5 != metadata['md5']:
  216. if file_info['state'] == 1:
  217. file_info['state'] = 3
  218. else:
  219. file_info['state'] = 2
  220. except Exception as e:
  221. logger.exception(e)
  222. pass
  223. file_info_list.append(file_info)
  224. return jsonify({'code': 1000, 'msg': '操作成功', 'data': file_info_list})
  225. @logger.catch()
  226. @app.route('/remove_file', methods=['POST'])
  227. def remove_file():
  228. file_name = request.get_json()['file_name']
  229. file_path = os.path.join(current_app.config['work_path'], file_name)
  230. if not os.path.exists(file_path):
  231. return jsonify({'code': '3006', 'msg': '文件不存在'})
  232. try:
  233. os.remove(file_path)
  234. os.remove(f'{file_path}.metadata')
  235. except Exception as e:
  236. logger.exception(e)
  237. return jsonify({'code': 3005, 'msg': '操作失败,无权限操作本地文件'})
  238. return jsonify({'code': 1000, 'msg': '操作成功'})
  239. @logger.catch()
  240. @app.route('/start_wps_server', methods=['POST'])
  241. def start_wps_server():
  242. url = f"ksowpscloudsvr://start=RelayHttpServer&serverId=aef5ac0d-d5a3-49ee-b02f-c31eeb063f9b"
  243. os.startfile(url)
  244. return jsonify({'code': 1000, 'msg': '操作成功'})
  245. @logger.catch()
  246. @app.route('/open_file', methods=['POST'])
  247. def open_file():
  248. file_name = request.get_json()['file_name']
  249. file_path = os.path.join(current_app.config['work_path'], file_name)
  250. if not os.path.exists(file_path):
  251. return jsonify({'code': '3006', 'msg': '文件不存在'})
  252. return open_file_by_wps(file_path)
  253. @logger.catch()
  254. @app.route('/create_cloud_file', methods=['POST'])
  255. def create_cloud_file():
  256. params = request.get_json()
  257. flag = app.config['serve_client'].create_cloud_file(local_file=params['local_file'], folder_id=params['folder_id'])
  258. return jsonify({'code': 1000, 'msg': '文件上传成功' if flag else '网络错误,上传失败。'})
  259. @logger.catch()
  260. @app.route('/get_folder_tree', methods=['POST'])
  261. def get_base_path():
  262. res = app.config['serve_client'].get_folder_tree()
  263. return jsonify(res)
  264. @logger.catch()
  265. @app.route('/get_token', methods=['POST'])
  266. def get_token():
  267. return jsonify(app.config['serve_client'].login_reply)
  268. @logger.catch()
  269. @app.route('/refresh_token', methods=['POST'])
  270. def refresh_token():
  271. app.config['serve_client'].login()
  272. return jsonify(app.config['serve_client'].login_reply)
  273. @logger.catch()
  274. @app.route('/download_cloud_file', methods=['POST'])
  275. def download_cloud_file():
  276. '''
  277. 1000: 操作完成
  278. 2001: 检测到文档正在被编辑,无法下载
  279. 2002: 检测到远程文件已经更新本地文件也发生改动,终止操作
  280. 3001: 不支持该类型的文件
  281. 3002: 下载文件失败
  282. :return:
  283. '''
  284. file_id = request.get_json()['file_id']
  285. is_template = request.get_json()['is_template']
  286. # 判断是否为exe模式执行
  287. if is_template:
  288. file_info = app.config['serve_client'].get_template_file_info(file_id)
  289. time_str = datetime.datetime.now().strftime('%Y-%m-%dT%H-%M-%S')
  290. local_path = os.path.join(current_app.config['work_path'],
  291. f'{file_info["name"]}({time_str}).{file_info["nameSuffix"]}')
  292. if app.config['serve_client'].download_template(file_info['fileLink'], local_path):
  293. return jsonify({'code': 1000, 'msg': '操作成功', 'file_path': local_path})
  294. else:
  295. return jsonify({'code': 3006, 'msg': '文件不存在'})
  296. file_info = app.config['serve_client'].get_file_info(file_id)
  297. if not file_info:
  298. return jsonify({'code': 3006, 'msg': '文件不存在'})
  299. local_file = os.path.join(current_app.config['work_path'], file_info['filePath'].replace('/', '_'))
  300. # 判断本地是否存在已下载的同名文件
  301. if not os.path.exists(local_file) or not os.path.exists(local_file + '.metadata.json'):
  302. try:
  303. app.config['serve_client'].download_file(file_info,
  304. current_app.config['work_path'])
  305. except Exception as e:
  306. logger.exception(e)
  307. return jsonify({'code': 3002, 'msg': '下载文件失败'})
  308. return jsonify({'code': 1000, 'msg': '操作成功', 'file_path': local_file})
  309. # 判断本地文件和线上文件是否一致
  310. local_file_metadata = current_app.config['serve_client'].load_metadata(local_file)
  311. if local_file_metadata['update_time'] == file_info['updateTime']:
  312. return jsonify({'code': 1000, 'msg': '操作成功', 'file_path': local_file})
  313. if local_file_metadata['md5'] == app.config['serve_client'].get_file_md5(local_file):
  314. app.config['serve_client'].download_file(file_info, current_app.config['work_path'])
  315. return jsonify({'code': 1000, 'msg': '操作成功', 'file_path': local_file})
  316. return jsonify({'code': 2002, 'msg': '检测到远程文件已经更新本地文件也发生改动,终止操作'})
  317. def start_flask(serve_client, work_path):
  318. app.config['serve_client'] = serve_client
  319. app.config['work_path'] = work_path
  320. app.run(host='127.0.0.1', port=5855)