123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452 |
- import datetime
- import gc
- import logging
- import os
- import shutil
- import socket
- import pythoncom
- import pywintypes
- import requests
- import win32com.client
- from flask import Flask, jsonify, request, current_app, send_from_directory, Response
- from flask_cors import CORS
- from requests_toolbelt import MultipartEncoder
- from win10toast import ToastNotifier
- from config import file_type_map
- from tools.check import is_file_open_in_wps
- from tools.file_manager import get_file_md5
- from tools.logger_handle import logger
- from tools.oss_client import oss_handle
- socket.getfqdn = lambda name=None: 'localhost'
- toaster = ToastNotifier()
- app = Flask(__name__, static_folder='../static')
- CORS(app, resources=r'/*')
- TARGET_URL = 'http://120.195.49.22:7215'
- class InterceptHandler(logging.Handler):
- def emit(self, record):
- # 将标准日志记录转为 loguru
- try:
- level = logger.level(record.levelname).name
- except ValueError:
- level = record.levelno
- logger.opt(depth=6, exception=record.exc_info).log(level, record.getMessage())
- logging.basicConfig(handlers=[InterceptHandler()], level=logging.INFO)
- for name in ("flask", "werkzeug", "watchdog"):
- logging.getLogger(name).handlers = [InterceptHandler()]
- logging.getLogger(name).propagate = False
- @app.route('/')
- def index():
- return send_from_directory(app.static_folder, 'index.html')
- # 代理静态文件(如 css, js, images 等)
- @app.route('/<path:path>')
- def static_proxy(path):
- return send_from_directory(app.static_folder, path)
- @logger.catch()
- @app.route('/pyapi/<path:path>', methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH'])
- def proxy(path):
- url = f'{TARGET_URL}/{path}'
- logger.info(f'接收到请求{url}')
- # 获取 headers(排除 Host 避免冲突)
- headers = {key: value for key, value in request.headers if key.lower() != 'host'}
- headers[
- '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'
- try:
- if request.files:
- # 构造 Multipart 表单
- fields = {}
- # 添加普通字段
- for key, value in request.form.items():
- fields[key] = value
- # 添加文件字段
- for key, file in request.files.items():
- fields[key] = (file.filename, file.stream, file.mimetype)
- m = MultipartEncoder(fields=fields)
- headers['Content-Type'] = m.content_type
- resp = requests.request(
- method=request.method,
- url=url,
- headers=headers,
- params=request.args,
- data=m,
- cookies=request.cookies,
- allow_redirects=False
- )
- else:
- json_data = None
- form_data = None
- if request.content_type and 'application/json' in request.content_type.lower():
- json_data = request.get_json(silent=True)
- else:
- form_data = request.form.to_dict()
- resp = requests.request(
- method=request.method,
- url=url,
- headers=headers,
- params=request.args,
- data=form_data,
- json=json_data,
- cookies=request.cookies,
- allow_redirects=False
- )
- except Exception as e:
- logger.error(e)
- return jsonify({'code': -1})
- # 构建 Flask 的响应
- excluded_headers = ['content-encoding', 'content-length', 'transfer-encoding', 'connection']
- response_headers = [(k, v) for k, v in resp.raw.headers.items() if k.lower() not in excluded_headers]
- return Response(resp.content, resp.status_code, response_headers)
- @logger.catch()
- def open_file_by_wps(file_path):
- ext = file_path.split(".")[-1]
- shutil.rmtree(win32com.client.gencache.GetGeneratePath())
- if ext not in file_type_map:
- return jsonify({'code': 3001, 'msg': '不支持该类型的文件'})
- if not os.path.exists(file_path):
- return jsonify({'code': 3006, 'msg': '文件不存在'})
- pythoncom.CoInitialize()
- try:
- wps = win32com.client.DispatchEx(file_type_map[ext][0])
- wps.Visible = True
- if ext in ['csv', 'xlsx', 'xls']:
- getattr(wps, file_type_map[ext][1]).Open(os.path.abspath(file_path))
- else:
- getattr(wps, file_type_map[ext][1]).open(os.path.abspath(file_path))
- except pywintypes.com_error as e:
- logger.exception(e)
- pass
- finally:
- del wps
- gc.collect()
- pythoncom.CoUninitialize()
- return jsonify({'code': 1000, 'msg': '操作完成'})
- @logger.catch()
- @app.route('/download_and_open_file', methods=['POST'])
- def download_and_open_with_wps():
- '''
- 1000: 操作完成
- 2001: 检测到文档正在被编辑,无法下载
- 2002: 检测到远程文件已经更新本地文件也发生改动,终止操作
- 3001: 不支持该类型的文件
- 3002: 下载文件失败
- :return:
- '''
- file_id = request.get_json()['file_id']
- is_template = request.get_json()['is_template']
- # 判断是否为exe模式执行
- if is_template:
- file_info = app.config['serve_client'].get_template_file_info(file_id)
- time_str = datetime.datetime.now().strftime('%Y-%m-%dT%H-%M-%S')
- local_path = os.path.join(current_app.config['work_path'],
- f'{file_info["name"]}({time_str}).{file_info["nameSuffix"]}')
- if app.config['serve_client'].download_template(file_info['fileLink'], local_path):
- return open_file_by_wps(local_path)
- else:
- return jsonify({'code': 3006, 'msg': '文件不存在'})
- file_info = app.config['serve_client'].get_file_info(file_id)
- if not file_info:
- return jsonify({'code': 3006, 'msg': '文件不存在'})
- local_file = os.path.join(current_app.config['work_path'], file_info['filePath'].replace('/', '_'))
- # 判断本地是否存在已下载的同名文件
- if not os.path.exists(local_file) or not os.path.exists(local_file + '.metadata.json'):
- app.config['serve_client'].download_file(file_info,
- current_app.config['work_path'])
- return open_file_by_wps(local_file)
- # 判断本地文件是否被打开
- if is_file_open_in_wps(local_file):
- return jsonify({'code': 1000, 'msg': '检测到文件已被打开'})
- # 判断本地文件和线上文件是否一致
- local_file_metadata = oss_handle.load_metadata(local_file)
- if local_file_metadata['update_time'] == file_info['updateTime']:
- return open_file_by_wps(local_file)
- if local_file_metadata['md5'] == app.config['serve_client'].get_file_md5(local_file):
- app.config['serve_client'].download_file(file_info, current_app.config['work_path'])
- return open_file_by_wps(local_file)
- return jsonify({'code': 2002, 'msg': '检测到远程文件已经更新本地文件也发生改动,终止操作'})
- @logger.catch()
- @app.route('/upload_local_file', methods=['POST'])
- def upload_local_file():
- file_name = request.get_json()['file_name']
- if is_file_open_in_wps(os.path.join(current_app.config['work_path'], file_name)):
- return jsonify({'code': 3004, 'msg': '操作失败,文件已被打开无法操作'})
- metadata = app.config['serve_client'].load_metadata(os.path.join(current_app.config['work_path'], file_name))
- code = app.config['serve_client'].upload_file(os.path.join(current_app.config['work_path'], file_name))
- if not code:
- return jsonify({'code': 3003, 'msg': '操作失败,上传文件失败'})
- try:
- os.remove(os.path.join(current_app.config['work_path'], file_name))
- os.remove(os.path.join(current_app.config['work_path'], file_name + '.metadata'))
- file_info = app.config['serve_client'].get_file_info(metadata['file_id'])
- if not file_info:
- return jsonify({'code': 3006, 'msg': '文件不存在'})
- app.config['serve_client'].download_file(file_info, current_app.config['work_path'])
- except Exception as e:
- logger.exception(e)
- return jsonify({'code': 1000, 'msg': '文件上传成功,本地文件清除失败'})
- return jsonify({'code': 1000, 'msg': '操作成功'})
- @logger.catch()
- @app.route('/update_local_file', methods=['POST'])
- def update_local_file():
- file_name = request.get_json()['file_name']
- if is_file_open_in_wps(os.path.join(current_app.config['work_path'], file_name)):
- return jsonify({'code': 3004, 'msg': '操作失败,文件已被打开无法操作'})
- try:
- metadata = oss_handle.load_metadata(os.path.join(current_app.config['work_path'], file_name))
- os.remove(os.path.join(current_app.config['work_path'], file_name))
- os.remove(os.path.join(current_app.config['work_path'], file_name + '.metadata'))
- except Exception as e:
- logger.exception(e)
- return jsonify({'code': 3005, 'msg': '操作失败,无权限操作本地文件'})
- try:
- file_info = app.config['serve_client'].get_file_info(metadata['file_id'])
- if not app.config['serve_client'].download_file(file_info, current_app.config['work_path']):
- return jsonify({'code': 3005, 'msg': '文件下载失败'})
- except Exception as e:
- logger.exception(e)
- return jsonify({'code': 3005, 'msg': '文件下载失败'})
- return jsonify({'code': 1000, 'msg': '操作成功'})
- @logger.catch()
- @app.route('/file_state_list', methods=['GET'])
- def file_state_list():
- '''
- state : 0 正常, 1: 云端有更新 2: 本地有变动且无冲突可以上传 3: 本地文件和云端文件有冲突
- :return:
- '''
- file_info_list = []
- for file_name in os.listdir(current_app.config['work_path']):
- suffer = file_name.split('.')[-1]
- if suffer not in file_type_map:
- continue
- if not os.path.exists(os.path.join(current_app.config['work_path'], file_name + '.metadata')):
- continue
- metadata = oss_handle.load_metadata(os.path.join(current_app.config['work_path'], file_name))
- file_info = {
- 'file_name': file_name,
- 'show_name': metadata['file_name'],
- 'cloud_update_time': metadata['update_time'],
- 'source_md5': metadata['md5'],
- 'state': 0
- }
- cloud_file_info = current_app.config['serve_client'].get_file_info(metadata['file_id'])
- if cloud_file_info == 2:
- file_info['state'] = 4
- file_info_list.append(file_info)
- continue
- if not cloud_file_info:
- file_info_list.append(file_info)
- continue
- if cloud_file_info['updateTime'] != metadata['update_time']:
- file_info['state'] = 1
- try:
- file_md5 = get_file_md5(os.path.join(current_app.config['work_path'], file_name))
- if file_md5 != metadata['md5']:
- if file_info['state'] == 1:
- file_info['state'] = 3
- else:
- file_info['state'] = 2
- except Exception as e:
- logger.exception(e)
- pass
- file_info_list.append(file_info)
- return jsonify({'code': 1000, 'msg': '操作成功', 'data': file_info_list})
- @logger.catch()
- @app.route('/remove_file', methods=['POST'])
- def remove_file():
- file_name = request.get_json()['file_name']
- file_path = os.path.join(current_app.config['work_path'], file_name)
- if not os.path.exists(file_path):
- return jsonify({'code': '3006', 'msg': '文件不存在'})
- try:
- os.remove(file_path)
- os.remove(f'{file_path}.metadata')
- except Exception as e:
- logger.exception(e)
- return jsonify({'code': 3005, 'msg': '操作失败,无权限操作本地文件'})
- return jsonify({'code': 1000, 'msg': '操作成功'})
- @logger.catch()
- @app.route('/start_wps_server', methods=['POST'])
- def start_wps_server():
- url = f"ksowpscloudsvr://start=RelayHttpServer&serverId=aef5ac0d-d5a3-49ee-b02f-c31eeb063f9b"
- os.startfile(url)
- return jsonify({'code': 1000, 'msg': '操作成功'})
- @logger.catch()
- @app.route('/open_file', methods=['POST'])
- def open_file():
- file_name = request.get_json()['file_name']
- file_path = os.path.join(current_app.config['work_path'], file_name)
- if not os.path.exists(file_path):
- return jsonify({'code': '3006', 'msg': '文件不存在'})
- if is_file_open_in_wps(file_path):
- return jsonify({'code': 1000, 'msg': '检测到文件已被打开'})
- return open_file_by_wps(file_path)
- @logger.catch()
- @app.route('/create_cloud_file', methods=['POST'])
- def create_cloud_file():
- params = request.get_json()
- flag = app.config['serve_client'].create_cloud_file(local_file=params['local_file'], folder_id=params['folder_id'])
- return jsonify({'code': 1000, 'msg': '文件上传成功' if flag else '网络错误,上传失败。'})
- @logger.catch()
- @app.route('/get_folder_tree', methods=['POST'])
- def get_base_path():
- return jsonify(app.config['serve_client'].get_folder_tree())
- @logger.catch()
- @app.route('/get_token', methods=['POST'])
- def get_token():
- return jsonify(app.config['serve_client'].login_reply)
- @logger.catch()
- @app.route('/refresh_token', methods=['POST'])
- def refresh_token():
- app.config['serve_client'].login()
- return jsonify(app.config['serve_client'].login_reply)
- @logger.catch()
- @app.route('/download_cloud_file', methods=['POST'])
- def download_cloud_file():
- '''
- 1000: 操作完成
- 2001: 检测到文档正在被编辑,无法下载
- 2002: 检测到远程文件已经更新本地文件也发生改动,终止操作
- 3001: 不支持该类型的文件
- 3002: 下载文件失败
- :return:
- '''
- file_id = request.get_json()['file_id']
- is_template = request.get_json()['is_template']
- # 判断是否为exe模式执行
- if is_template:
- file_info = app.config['serve_client'].get_template_file_info(file_id)
- time_str = datetime.datetime.now().strftime('%Y-%m-%dT%H-%M-%S')
- local_path = os.path.join(current_app.config['work_path'],
- f'{file_info["name"]}({time_str}).{file_info["nameSuffix"]}')
- if app.config['serve_client'].download_template(file_info['fileLink'], local_path):
- return jsonify({'code': 1000, 'msg': '操作成功', 'file_path': local_path})
- else:
- return jsonify({'code': 3006, 'msg': '文件不存在'})
- file_info = app.config['serve_client'].get_file_info(file_id)
- if not file_info:
- return jsonify({'code': 3006, 'msg': '文件不存在'})
- local_file = os.path.join(current_app.config['work_path'], file_info['filePath'].replace('/', '_'))
- # 判断本地是否存在已下载的同名文件
- if not os.path.exists(local_file) or not os.path.exists(local_file + '.metadata.json'):
- try:
- app.config['serve_client'].download_file(file_info,
- current_app.config['work_path'])
- except Exception as e:
- logger.exception(e)
- return jsonify({'code': 3002, 'msg': '下载文件失败'})
- return jsonify({'code': 1000, 'msg': '操作成功', 'file_path': local_file})
- # 判断本地文件是否被打开
- if is_file_open_in_wps(local_file):
- return jsonify({'code': 2001, 'msg': '检测到文档正在被编辑,无法下载'})
- # 判断本地文件和线上文件是否一致
- local_file_metadata = oss_handle.load_metadata(local_file)
- if local_file_metadata['update_time'] == file_info['updateTime']:
- return jsonify({'code': 1000, 'msg': '操作成功', 'file_path': local_file})
- if local_file_metadata['md5'] == app.config['serve_client'].get_file_md5(local_file):
- app.config['serve_client'].download_file(file_info, current_app.config['work_path'])
- return jsonify({'code': 1000, 'msg': '操作成功', 'file_path': local_file})
- return jsonify({'code': 2002, 'msg': '检测到远程文件已经更新本地文件也发生改动,终止操作'})
- def start_flask(serve_client, work_path):
- app.config['serve_client'] = serve_client
- app.config['work_path'] = work_path
- app.run(host='127.0.0.1', port=5855)
|