api_app.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. import ctypes
  2. import logging
  3. import os
  4. import shutil
  5. import site
  6. import sys
  7. import pythoncom
  8. import pywintypes
  9. import win32com.client
  10. from flask import Flask, jsonify, request, current_app
  11. from flask_cors import CORS
  12. from win10toast import ToastNotifier
  13. from core.wps_handle import is_file_open_in_wps, bring_wps_window_to_front
  14. from tools.check import detect_oss_file_changes, detect_local_file_changes
  15. from tools.config import get_config, file_type_map
  16. from tools.file_manager import get_file_md5
  17. from tools.logger_handle import logger
  18. from tools.oss_client import oss_handle
  19. from tools.serve_client import ServerClient
  20. toaster = ToastNotifier()
  21. app = Flask(__name__)
  22. class InterceptHandler(logging.Handler):
  23. def emit(self, record):
  24. # 将标准日志记录转为 loguru
  25. try:
  26. level = logger.level(record.levelname).name
  27. except ValueError:
  28. level = record.levelno
  29. logger.opt(depth=6, exception=record.exc_info).log(level, record.getMessage())
  30. logging.basicConfig(handlers=[InterceptHandler()], level=logging.INFO)
  31. for name in ("flask", "werkzeug", "watchdog"):
  32. logging.getLogger(name).handlers = [InterceptHandler()]
  33. logging.getLogger(name).propagate = False
  34. CORS(app)
  35. @logger.catch()
  36. def open_file_by_wps(file_path):
  37. ext = file_path.split(".")[-1]
  38. shutil.rmtree(win32com.client.gencache.GetGeneratePath())
  39. if ext not in file_type_map:
  40. return jsonify({'code': 3001, 'msg': '不支持该类型的文件'})
  41. if not os.path.exists(file_path):
  42. return jsonify({'code': 3006, 'msg': '文件不存在'})
  43. pythoncom.CoInitialize()
  44. try:
  45. if ext in ['csv', 'xlsx', 'xls']:
  46. # wps = win32com.client.gencache.EnsureDispatch(file_type_map['xlsx'][0])
  47. wps = win32com.client.Dispatch(file_type_map[ext][0])
  48. wps.Visible = True
  49. getattr(wps, file_type_map[ext][1]).Open(os.path.abspath(file_path))
  50. else:
  51. wps = win32com.client.Dispatch(file_type_map[ext][0])
  52. wps.Visible = True
  53. getattr(wps, file_type_map[ext][1]).open(os.path.abspath(file_path))
  54. except pywintypes.com_error as e:
  55. logger.exception(e)
  56. bring_wps_window_to_front(file_path)
  57. pythoncom.CoUninitialize()
  58. return jsonify({'code': 1000, 'msg': '操作完成'})
  59. @logger.catch()
  60. @app.route('/download_and_open_file', methods=['POST'])
  61. def download_and_open_with_wps():
  62. '''
  63. 1000: 操作完成
  64. 2001: 检测到文档正在被编辑,无法下载
  65. 2002: 检测到远程文件已经更新本地文件也发生改动,终止操作
  66. 3001: 不支持该类型的文件
  67. 3002: 下载文件失败
  68. :return:
  69. '''
  70. file_id = request.get_json()['file_id']
  71. # 判断是否为exe模式执行
  72. file_info = app.config['serve_client'].get_file_info(file_id)
  73. if not file_info:
  74. return jsonify({'code': 3006, 'msg': '文件不存在'})
  75. local_file = os.path.join(current_app.config['work_path'], file_info['filePath'].replace('/', '_'))
  76. # 判断本地是否存在已下载的同名文件
  77. if not os.path.exists(local_file) or not os.path.exists(local_file + '.metadata.json'):
  78. app.config['serve_client'].download_file(file_info,
  79. current_app.config['work_path'])
  80. return open_file_by_wps(local_file)
  81. # 判断本地文件是否被打开
  82. if is_file_open_in_wps(local_file):
  83. bring_wps_window_to_front(local_file)
  84. return jsonify({'code': 1000, 'msg': '检测到文件已被打开'})
  85. # 判断本地文件和线上文件是否一致
  86. local_file_metadata = oss_handle.load_metadata(local_file)
  87. if local_file_metadata['update_time'] == file_info['updateTime']:
  88. return open_file_by_wps(local_file)
  89. if local_file_metadata['md5'] == app.config['serve_client'].get_file_md5(local_file):
  90. app.config['serve_client'].download_file(file_info, current_app.config['work_path'])
  91. return open_file_by_wps(local_file)
  92. return jsonify({'code': 2002, 'msg': '检测到远程文件已经更新本地文件也发生改动,终止操作'})
  93. @logger.catch()
  94. @app.route('/upload_local_file', methods=['POST'])
  95. def upload_local_file():
  96. file_name = request.get_json()['file_name']
  97. if is_file_open_in_wps(os.path.join(current_app.config['work_path'], file_name)):
  98. return jsonify({'code': 3004, 'msg': '操作失败,文件已被打开无法操作'})
  99. metadata = app.config['serve_client'].load_metadata(os.path.join(current_app.config['work_path'], file_name))
  100. code = app.config['serve_client'].upload_file(os.path.join(current_app.config['work_path'], file_name))
  101. if not code:
  102. return jsonify({'code': 3003, 'msg': '操作失败,上传文件失败'})
  103. try:
  104. os.remove(os.path.join(current_app.config['work_path'], file_name))
  105. os.remove(os.path.join(current_app.config['work_path'], file_name + '.metadata'))
  106. file_info = app.config['serve_client'].get_file_info(metadata['file_id'])
  107. if not file_info:
  108. return jsonify({'code': 3006, 'msg': '文件不存在'})
  109. app.config['serve_client'].download_file(file_info, current_app.config['work_path'])
  110. except Exception as e:
  111. logger.exception(e)
  112. return jsonify({'code': 1000, 'msg': '文件上传成功,本地文件清除失败'})
  113. return jsonify({'code': 1000, 'msg': '操作成功'})
  114. @logger.catch()
  115. @app.route('/update_local_file', methods=['POST'])
  116. def update_local_file():
  117. file_name = request.get_json()['file_name']
  118. if is_file_open_in_wps(os.path.join(current_app.config['work_path'], file_name)):
  119. return jsonify({'code': 3004, 'msg': '操作失败,文件已被打开无法操作'})
  120. try:
  121. metadata = oss_handle.load_metadata(os.path.join(current_app.config['work_path'], file_name))
  122. os.remove(os.path.join(current_app.config['work_path'], file_name))
  123. os.remove(os.path.join(current_app.config['work_path'], file_name + '.metadata'))
  124. except Exception as e:
  125. logger.exception(e)
  126. return jsonify({'code': 3005, 'msg': '操作失败,无权限操作本地文件'})
  127. try:
  128. file_info = app.config['serve_client'].get_file_info(metadata['file_id'])
  129. if not app.config['serve_client'].download_file(file_info, current_app.config['work_path']):
  130. return jsonify({'code': 3005, 'msg': '文件下载失败'})
  131. except Exception as e:
  132. logger.exception(e)
  133. return jsonify({'code': 3005, 'msg': '文件下载失败'})
  134. return jsonify({'code': 1000, 'msg': '操作成功'})
  135. @logger.catch()
  136. @app.route('/file_state_list', methods=['GET'])
  137. def file_state_list():
  138. '''
  139. state : 0 正常, 1: 云端有更新 2: 本地有变动且无冲突可以上传 3: 本地文件和云端文件有冲突
  140. :return:
  141. '''
  142. file_info_list = []
  143. for file_name in os.listdir(current_app.config['work_path']):
  144. suffer = file_name.split('.')[-1]
  145. if suffer not in file_type_map:
  146. continue
  147. if not os.path.exists(os.path.join(current_app.config['work_path'], file_name + '.metadata')):
  148. continue
  149. metadata = oss_handle.load_metadata(os.path.join(current_app.config['work_path'], file_name))
  150. file_info = {
  151. 'file_name': file_name,
  152. 'show_name': metadata['file_name'],
  153. 'cloud_update_time': metadata['update_time'],
  154. 'source_md5': metadata['md5'],
  155. 'state': 0
  156. }
  157. cloud_file_info = current_app.config['serve_client'].get_file_info(metadata['file_id'])
  158. if cloud_file_info == 2:
  159. file_info['state'] = 4
  160. file_info_list.append(file_info)
  161. continue
  162. if not cloud_file_info:
  163. file_info_list.append(file_info)
  164. continue
  165. if cloud_file_info['updateTime'] != metadata['update_time']:
  166. file_info['state'] = 1
  167. try:
  168. file_md5 = get_file_md5(os.path.join(current_app.config['work_path'], file_name))
  169. if file_md5 != metadata['md5']:
  170. if file_info['state'] == 1:
  171. file_info['state'] = 3
  172. else:
  173. file_info['state'] = 2
  174. except Exception as e:
  175. logger.exception(e)
  176. file_info_list.append(file_info)
  177. return jsonify({'code': 1000, 'msg': '操作成功', 'data': file_info_list})
  178. @logger.catch()
  179. @app.route('/remove_file', methods=['POST'])
  180. def remove_file():
  181. file_name = request.get_json()['file_name']
  182. file_path = os.path.join(current_app.config['work_path'], file_name)
  183. if not os.path.exists(file_path):
  184. return jsonify({'code': '3006', 'msg': '文件不存在'})
  185. try:
  186. os.remove(file_path)
  187. os.remove(f'{file_path}.metadata')
  188. except Exception as e:
  189. logger.exception(e)
  190. return jsonify({'code': 3005, 'msg': '操作失败,无权限操作本地文件'})
  191. return jsonify({'code': 1000, 'msg': '操作成功'})
  192. @logger.catch()
  193. @app.route('/open_file', methods=['POST'])
  194. def open_file():
  195. file_name = request.get_json()['file_name']
  196. file_path = os.path.join(current_app.config['work_path'], file_name)
  197. if not os.path.exists(file_path):
  198. return jsonify({'code': '3006', 'msg': '文件不存在'})
  199. if is_file_open_in_wps(file_path):
  200. bring_wps_window_to_front(file_path)
  201. return jsonify({'code': 1000, 'msg': '检测到文件已被打开'})
  202. return open_file_by_wps(file_path)
  203. def start_flask(serve_client, work_path):
  204. app.config['serve_client'] = serve_client
  205. app.config['work_path'] = work_path
  206. # app.logger.handlers.clear()
  207. # app.logger.addHandler(InterceptHandler())
  208. app.run(host='0.0.0.0', port=5855)