api_app.py 15 KB

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