api_app.py 9.6 KB

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