Ver Fonte

更换网页调用本地回环逻辑,使用自定义协议链接,URL Scheme为OfficeHelperTransfer

dukuisong há 1 semana atrás
pai
commit
fb99266314

+ 154 - 0
OfficeHelperTransfer.py

@@ -0,0 +1,154 @@
+# download_and_open.py
+import os
+import sys
+import requests
+import json
+import urllib.parse
+from tkinter import messagebox, Tk
+
+# 服务地址
+SERVICE_URL = "http://127.0.0.1:58890/download_and_open_file"
+dl_code = os.path.realpath(sys.argv[0])[:1]
+
+local_path = os.path.dirname(os.path.realpath(sys.argv[0]))
+
+
+if not os.path.exists(os.path.join(local_path, 'config.json')):
+    server_port = 25855
+else:
+    with open(os.path.join(local_path, 'config.json'), 'r') as f:
+        config_data = json.load(f)
+    server_port = config_data.get('port')
+    if not server_port:
+        server_port = 25855
+
+def parse_args():
+    """解析命令行参数,支持两种格式:
+    1. URL 格式: myapp://action?file_id=xxx&is_template=1
+    2. 直接参数: --file_id=xxx --is_template=1
+    """
+    args = {}
+
+    if len(sys.argv) < 2:
+        return args
+
+    # 获取完整命令行
+    cmd = ' '.join(sys.argv[1:])
+
+    # 方式一:URL 格式解析
+    if '?' in cmd:
+        # 提取查询参数部分
+        query_part = cmd.split('?')[-1]
+        for pair in query_part.split('&'):
+            if '=' in pair:
+                key, value = pair.split('=', 1)
+                args[key] = urllib.parse.unquote(value)
+    else:
+        # 方式二:命令行参数格式解析
+        for arg in sys.argv[1:]:
+            if arg.startswith('--') and '=' in arg:
+                key, value = arg[2:].split('=', 1)
+                args[key] = value
+
+    return args
+
+
+def is_service_running(server_port):
+    """检查本地服务是否可用"""
+    try:
+        # 尝试连接服务,设置3秒超时
+        resp = requests.get(f"http://127.0.0.1:{server_port}/health", timeout=3)
+        return resp.status_code == 200
+    except requests.exceptions.RequestException:
+        return False
+
+
+def show_error(message):
+    """显示错误弹窗(使用 tkinter,Python 自带)"""
+    root = Tk()
+    root.withdraw()  # 隐藏主窗口
+    root.attributes('-topmost', True)
+    messagebox.showerror("错误", message)
+    root.destroy()
+
+
+def show_info(message):
+    """显示信息弹窗"""
+    root = Tk()
+    root.withdraw()
+    root.attributes('-topmost', True)
+    messagebox.showinfo("提示", message)
+    root.destroy()
+
+
+def send_request(file_id, is_template, server_port):
+    """发送 POST 请求到本地服务"""
+    payload = {
+        "file_id": file_id,
+        "is_template": is_template
+    }
+
+    try:
+        resp = requests.post(
+            f'http://127.0.0.1:{server_port}/download_and_open_file',
+            json=payload,
+            timeout=10
+        )
+
+        if resp.status_code == 200:
+            return True, resp.text
+        else:
+            return False, f"服务返回错误码: {resp.status_code}"
+
+    except requests.exceptions.ConnectionError:
+        return False, "无法连接到服务"
+    except requests.exceptions.Timeout:
+        return False, "请求超时"
+    except Exception as e:
+        return False, f"请求异常: {str(e)}"
+
+
+def main():
+    # 1. 解析参数
+    args = parse_args()
+
+    file_id = args.get('file_id')
+    is_template = args.get('is_template')
+
+    # 参数验证
+    if not file_id:
+        show_error("缺少必要参数: file_id")
+        return 1
+
+    if is_template is None:
+        show_error("缺少必要参数: is_template")
+        return 1
+
+    # 尝试转换 is_template 为数字(如果传入的是字符串)
+    try:
+        is_template_val = int(is_template) if is_template else 0
+    except ValueError:
+        is_template_val = 1 if is_template.lower() in ('true', '1', 'yes') else 0
+
+    # 2. 检查服务是否运行
+    if not is_service_running(server_port):
+        show_error(
+            "本地服务未启动!\n\n"
+            "请确保服务已运行后重试。\n\n"
+            "如需帮助,请联系技术支持。"
+        )
+        return 1
+
+    # 3. 发送请求
+    success, result = send_request(file_id, is_template_val, server_port)
+
+    if success:
+        # 请求成功,静默退出(不弹窗,避免打扰用户)
+        return 0
+    else:
+        show_error(f"请求失败!\n\n{result}\n\n请检查服务状态后重试。")
+        return 1
+
+
+if __name__ == "__main__":
+    sys.exit(main())

+ 84 - 38
api/api_app.py

@@ -2,7 +2,8 @@ import datetime
 import gc
 import gc
 import logging
 import logging
 import os
 import os
-import shutil
+import subprocess
+import time
 import socket
 import socket
 
 
 import pythoncom
 import pythoncom
@@ -15,8 +16,8 @@ from flask_cors import CORS
 from requests_toolbelt import MultipartEncoder
 from requests_toolbelt import MultipartEncoder
 from win10toast import ToastNotifier
 from win10toast import ToastNotifier
 
 
-from config import file_type_map, TARGET_URL, SERVER_POINT
-from tools.check import is_file_open_in_wps
+from config import file_type_map, TARGET_URL, SERVER_POINT, API_SERVER_PORT
+from tools.check import is_file_open_in_wps, enable_wps_login_force
 from tools.file_manager import get_file_md5
 from tools.file_manager import get_file_md5
 from tools.logger_handle import logger
 from tools.logger_handle import logger
 
 
@@ -27,21 +28,31 @@ app = Flask(__name__, static_folder='../static')
 
 
 CORS(app, resources=r'/*')
 CORS(app, resources=r'/*')
 
 
-
 class InterceptHandler(logging.Handler):
 class InterceptHandler(logging.Handler):
     def emit(self, record):
     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
+        # 获取对应的 loguru 级别
+        logger_opt = logger.opt(depth=6, exception=record.exc_info)
+        logger_opt.log(record.levelname, record.getMessage())
+
+# 移除 Flask 默认的所有 Handler,并添加自定义的 InterceptHandler
+app.logger.handlers = []
+app.logger.addHandler(InterceptHandler())
+app.logger.setLevel(logging.INFO)
+
+# 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('/')
 @app.route('/')
@@ -159,36 +170,63 @@ def proxy(router_path):
 
 
     return Response(resp.content, resp.status_code, response_headers)
     return Response(resp.content, resp.status_code, response_headers)
 
 
+@logger.catch()
+@app.route('/health', methods=['GET'])
+def health():
+    return {'code': 200, 'msg': 'health'}
+
 
 
 @logger.catch()
 @logger.catch()
 def open_file_by_wps(file_path):
 def open_file_by_wps(file_path):
-    ext = file_path.split(".")[-1]
-    shutil.rmtree(win32com.client.gencache.GetGeneratePath())
+    enable_wps_login_force()
+    ext = os.path.splitext(file_path)[1][1:].lower()
     if ext not in file_type_map:
     if ext not in file_type_map:
         return {'code': 3001, 'msg': '不支持该类型的文件'}
         return {'code': 3001, 'msg': '不支持该类型的文件'}
 
 
     if not os.path.exists(file_path):
     if not os.path.exists(file_path):
         return {'code': 3006, 'msg': '文件不存在'}
         return {'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))
+    # 先检测 WPS 是否可用(可缓存结果,避免重复检测)
+    # if not is_wps_available():
+    #     return {'code': 3007, 'msg': '未检测到可用的 WPS Office,请安装 WPS 并确保已正确注册 COM 组件'}
 
 
-    except pywintypes.com_error as e:
-        logger.exception(e)
-        pass
-
-    finally:
-        del wps
-        gc.collect()
-        pythoncom.CoUninitialize()
+    try:
+        subprocess.Popen([file_type_map[ext][2], file_path])
+        return {'code': 1000, 'msg': '操作完成'}
 
 
-    return {'code': 1000, 'msg': '操作完成'}
+    except Exception as e:
+        logger.exception(f"未知错误: {e}")
+        return {'code': 3009, 'msg': f'打开文件时发生未知错误: {e}'}
+
+    # ext = os.path.splitext(file_path)[1][1:].lower()
+    # if ext not in file_type_map:
+    #     return {'code': 3001, 'msg': '不支持该类型的文件'}
+    #
+    # if not os.path.exists(file_path):
+    #     return {'code': 3006, 'msg': '文件不存在'}
+    #
+    # # 先检测 WPS 是否可用(可缓存结果,避免重复检测)
+    # if not is_wps_available():
+    #     return {'code': 3007, 'msg': '未检测到可用的 WPS Office,请安装 WPS 并确保已正确注册 COM 组件'}
+    #
+    # progid, method_name = file_type_map[ext]
+    #
+    # pythoncom.CoInitialize()
+    # app = None
+    # try:
+    #     app = win32com.client.Dispatch(progid)
+    #     app.Visible = True
+    #     # 调用对应方法打开文件
+    #     getattr(app, method_name).Open(os.path.abspath(file_path))
+    #     return {'code': 1000, 'msg': '操作完成'}
+    # except pywintypes.com_error as e:
+    #     logger.exception(f"WPS COM 操作失败: {e}")
+    #     return {'code': 3008, 'msg': f'WPS 打开文件失败,请检查文件是否损坏或 WPS 是否正常工作。错误: {e}'}
+    # except Exception as e:
+    #     logger.exception(f"未知错误: {e}")
+    #     return {'code': 3009, 'msg': f'打开文件时发生未知错误: {e}'}
+    # finally:
+    #     pythoncom.CoUninitialize()
 
 
 
 
 @logger.catch()
 @logger.catch()
@@ -378,8 +416,16 @@ def remove_file():
 @logger.catch()
 @logger.catch()
 @app.route('/start_wps_server', methods=['POST'])
 @app.route('/start_wps_server', methods=['POST'])
 def start_wps_server():
 def start_wps_server():
-    url = f"ksowpscloudsvr://start=RelayHttpServer&serverId=aef5ac0d-d5a3-49ee-b02f-c31eeb063f9b"
-    os.startfile(url)
+    logger.info(f'start_wps_server:')
+    try:
+        url = f"ksowpscloudsvr://start=RelayHttpServer&serverId=aef5ac0d-d5a3-49ee-b02f-c31eeb063f9b"
+        os.startfile(url)
+        time.sleep(1)
+        requests.post('http://127.0.0.1:58890/version', data='{"serverId":"aef5ac0d-d5a3-49ee-b02f-c31eeb063f9b"}')
+        requests.post('http://127.0.0.1:58890/redirect/runParams', data='{"serverId":"9b6f627b-68cc-40e9-8cae-2921f01d4cd9","data":"eyJtZXRob2QiOiJnZXQiLCJ1cmwiOiJodHRwOi8vMTI3LjAuMC4xOjU4NTUvcmliYm9uLnhtbCIsImRhdGEiOiIifQ=="}')
+    except Exception as e:
+        logger.error('start_wps_server with an error ')
+        logger.exception(e)
     return jsonify({'code': 1000, 'msg': '操作成功'})
     return jsonify({'code': 1000, 'msg': '操作成功'})
 
 
 
 
@@ -505,6 +551,6 @@ def start_flask(serve_client, work_path):
     app.config['serve_client'] = serve_client
     app.config['serve_client'] = serve_client
     app.config['work_path'] = work_path
     app.config['work_path'] = work_path
     try:
     try:
-        app.run(host='127.0.0.1', port=5855)
+        app.run(host='127.0.0.1', port=API_SERVER_PORT)
     except KeyboardInterrupt:
     except KeyboardInterrupt:
         app.shutdown()
         app.shutdown()

+ 2 - 3
api/ws_app.py

@@ -5,7 +5,7 @@ import os
 from websocket_server import WebsocketServer
 from websocket_server import WebsocketServer
 
 
 from api.api_app import open_file_by_wps
 from api.api_app import open_file_by_wps
-from config import args
+from config import args, WS_SERVER_PORT
 from tools.serve_client import server_client
 from tools.serve_client import server_client
 from tools.check import is_file_open_in_wps
 from tools.check import is_file_open_in_wps
 from tools.logger_handle import logger
 from tools.logger_handle import logger
@@ -76,12 +76,11 @@ def on_message(client, server, data):
 
 
 
 
 def start_ws_server():
 def start_ws_server():
-    ws_server = WebsocketServer(host='0.0.0.0', port=5856)
+    ws_server = WebsocketServer(host='0.0.0.0', port=WS_SERVER_PORT)
     ws_server.set_fn_message_received(on_message)
     ws_server.set_fn_message_received(on_message)
 
 
     logger.info('ws server start')
     logger.info('ws server start')
     ws_server.run_forever()
     ws_server.run_forever()
 
 
-
 if __name__ == '__main__':
 if __name__ == '__main__':
     start_ws_server()
     start_ws_server()

+ 5 - 1
config.json

@@ -1 +1,5 @@
-{"username": "admin", "password": "jxkj123456", "worker_path": "E:\\\\PycharmProjects\\\\office_plugin\\\\tools\\\\storage", "server_url": "http://221.226.41.58:7215"}
+{
+  "ip": "127.0.0.1",
+  "port": 25855,
+  "wsPort": 25856
+}

+ 25 - 11
config.py

@@ -2,12 +2,28 @@ import json
 import os
 import os
 import sys
 import sys
 
 
-VERSION = '1.3.0'
+from tools.wps_plugin_contorl import get_wps_exe_path
 
 
+VERSION = '1.3.3'
+
+WPS_WORD = get_wps_exe_path('wps')
+WPS_EXCEL = get_wps_exe_path('et')
+WPS_PPT= get_wps_exe_path('wpp')
+
+local_path = os.path.dirname(os.path.realpath(sys.argv[0]))
 
 
 dl_code = os.path.realpath(sys.argv[0])[:1]
 dl_code = os.path.realpath(sys.argv[0])[:1]
 # dl_code = 'd'
 # dl_code = 'd'
 
 
+if not os.path.exists(os.path.join(local_path, 'config.json')):
+    WS_SERVER_PORT = 25856
+    API_SERVER_PORT = 25855
+else:
+    with open(os.path.join(local_path, 'config.json'), 'r') as f:
+        config_data = json.load(f)
+    WS_SERVER_PORT = config_data['wsPort']
+    API_SERVER_PORT = config_data['port']
+
 if not os.path.exists(f'{dl_code}:\\ProgramData\\OfficeAssistant\\config.json'):
 if not os.path.exists(f'{dl_code}:\\ProgramData\\OfficeAssistant\\config.json'):
     sys.exit(0)
     sys.exit(0)
 
 
@@ -22,8 +38,6 @@ if args.get('server_url'):
 else:
 else:
     SERVER_POINT = 'http://221.226.41.58:7215/'
     SERVER_POINT = 'http://221.226.41.58:7215/'
     TARGET_URL = 'http://221.226.41.58:7215'
     TARGET_URL = 'http://221.226.41.58:7215'
-    # SERVER_POINT = 'http://192.168.30.1:7215/'
-    # TARGET_URL = 'http://192.168.30.1:7215'
 
 
 
 
 
 
@@ -32,14 +46,14 @@ def is_frozen():
 
 
 
 
 file_type_map = {
 file_type_map = {
-    'docx': ['kwps.Application', 'Documents'],
-    'doc': ['kwps.Application', 'Documents'],
-    'txt': ['kwps.Application', 'Documents'],
-    'ppt': ['kwpp.Application', 'Presentations'],
-    'pptx': ['kwpp.Application', 'Presentations'],
-    'csv': ['ket.Application', 'Workbooks'],
-    'xlsx': ['ket.Application', 'Workbooks'],
-    'xls': ['ket.Application', 'Workbooks'],
+    'docx': ['kwps.Application', 'Documents', WPS_WORD],
+    'doc': ['kwps.Application', 'Documents', WPS_WORD],
+    'txt': ['kwps.Application', 'Documents', WPS_WORD],
+    'ppt': ['kwpp.Application', 'Presentations', WPS_PPT],
+    'pptx': ['kwpp.Application', 'Presentations', WPS_PPT],
+    'csv': ['ket.Application', 'Workbooks', WPS_EXCEL],
+    'xlsx': ['ket.Application', 'Workbooks', WPS_EXCEL],
+    'xls': ['ket.Application', 'Workbooks', WPS_EXCEL],
 }
 }
 
 
 headers = {
 headers = {

+ 7 - 5
office_helper.py

@@ -6,6 +6,7 @@ import threading
 import time
 import time
 
 
 from api.ws_app import start_ws_server
 from api.ws_app import start_ws_server
+from tools.check import enable_wps_login_force
 
 
 if getattr(sys, 'frozen', False):
 if getattr(sys, 'frozen', False):
     # 打包后的 exe
     # 打包后的 exe
@@ -40,19 +41,20 @@ def ApplicationInstance(func):
 
 
 # 启动所有后台服务
 # 启动所有后台服务
 @ApplicationInstance
 @ApplicationInstance
-def start_all_services(serve_client, work_path):
+def start_all_services(serve_client, storage_path):
+    enable_wps_login_force()
     # 启动 Flask 服务
     # 启动 Flask 服务
-    threading.Thread(target=start_flask, args=[serve_client, work_path], daemon=True).start()
+    threading.Thread(target=start_flask, args=[serve_client, storage_path], daemon=True).start()
     # 启动文件监控
     # 启动文件监控
-    threading.Thread(target=start_watchdog, args=[serve_client, work_path], daemon=True).start()
-    threading.Thread(target=start_ws_server, daemon=True).start()
+    # threading.Thread(target=start_watchdog, args=[serve_client, storage_path], daemon=True).start()
+
+    # threading.Thread(target=start_ws_server, daemon=True).start()
     logger.info('server running')
     logger.info('server running')
 
 
     while True:
     while True:
         time.sleep(10)
         time.sleep(10)
 
 
 
 
-
 if __name__ == "__main__":
 if __name__ == "__main__":
     if not (args.get('username') and args.get('password')):
     if not (args.get('username') and args.get('password')):
         logger.error('The config is missing critical information')
         logger.error('The config is missing critical information')

+ 3 - 2
office_helper.spec

@@ -25,13 +25,14 @@ exe = EXE(
     debug=False,
     debug=False,
     bootloader_ignore_signals=False,
     bootloader_ignore_signals=False,
     strip=False,
     strip=False,
-    upx=True,
+    upx=False,
     console=False,
     console=False,
     disable_windowed_traceback=False,
     disable_windowed_traceback=False,
     argv_emulation=False,
     argv_emulation=False,
     target_arch=None,
     target_arch=None,
     codesign_identity=None,
     codesign_identity=None,
     entitlements_file=None,
     entitlements_file=None,
+    uac_admin=True,
     icon=['icon.ico'],
     icon=['icon.ico'],
 )
 )
 coll = COLLECT(
 coll = COLLECT(
@@ -39,7 +40,7 @@ coll = COLLECT(
     a.binaries,
     a.binaries,
     a.datas,
     a.datas,
     strip=False,
     strip=False,
-    upx=True,
+    upx=False,
     upx_exclude=[],
     upx_exclude=[],
     name='office_helper',
     name='office_helper',
 )
 )

+ 2 - 3
readme

@@ -28,10 +28,9 @@ upload_file
 上传本地文件覆盖oss端文件\
 上传本地文件覆盖oss端文件\
 
 
 pyinstaller --onefile --noconsole --windowed --icon=../icon.ico init_wps_plug.py
 pyinstaller --onefile --noconsole --windowed --icon=../icon.ico init_wps_plug.py
+pyinstaller --onefile --noconsole --windowed --icon=icon.ico OfficeHelperTransfer.py
 
 
-pyinstaller --noconsole --noupx --windowed --add-data "static:static" --icon=icon.ico office_helper.py
+pyinstaller --uac-admin --noconsole --noupx --windowed --add-data "static:static" --icon=icon.ico office_helper.py
 
 
 
 
 pyinstaller --onefile --noconsole  --windowed --add-data "icon.ico;." --icon=icon.ico upload_file.py
 pyinstaller --onefile --noconsole  --windowed --add-data "icon.ico;." --icon=icon.ico upload_file.py
-
-

+ 8 - 0
register.reg

@@ -0,0 +1,8 @@
+Windows Registry Editor Version 5.00
+
+[HKEY_CLASSES_ROOT\OfficeHelperTransfer]
+@="OfficeHelperTransfer Protocol"
+"URL Protocol"=""
+
+[HKEY_CLASSES_ROOT\OfficeHelperTransfer\shell\open\command]
+@="\"E:\\PycharmProjects\\office_plugin\\dist\\OfficeHelperTransfer.exe\" \"%1\""

+ 0 - 1
static/assets/TaskPane-DOu8Yl8G.css

@@ -1 +0,0 @@
-.global[data-v-f5bae935]{font-size:15px;min-height:95%}.divItem[data-v-f5bae935]{margin-left:5px;margin-bottom:18px;font-size:15px;word-wrap:break-word}

Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 4
static/assets/editor.worker-CDU2Z2yo.js


BIN
static/assets/editor.worker-CDU2Z2yo.js.gz


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 4
static/assets/html.worker-ujR1sCIh.js


BIN
static/assets/html.worker-ujR1sCIh.js.gz


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
static/assets/index-BEQAF1GO.css


BIN
static/assets/index-BEQAF1GO.css.gz


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
static/assets/index-CFSY6fn5.css


BIN
static/assets/index-CFSY6fn5.css.gz


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
static/assets/index-Ci9C6Xj5.css


BIN
static/assets/index-Ci9C6Xj5.css.gz


+ 0 - 1
static/assets/index-DlzDd1un.css

@@ -1 +0,0 @@
-.vue-office-docx{height:100%;overflow-y:auto}.vue-office-docx .docx-wrapper>section.docx{margin-bottom:5px}@media screen and (max-width: 800px){.vue-office-docx .docx-wrapper{padding:10px}.vue-office-docx .docx-wrapper>section.docx{padding:10px!important;width:100%!important}}

Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
static/assets/video-SybPbBtI.js


BIN
static/assets/video-SybPbBtI.js.gz


+ 2 - 2
static/index.html

@@ -205,8 +205,8 @@
         opacity: 0.5;
         opacity: 0.5;
       }
       }
     </style>
     </style>
-    <script type="module" crossorigin src="./assets/index-IGEjQt3_.js"></script>
-    <link rel="stylesheet" crossorigin href="./assets/index-CFSY6fn5.css">
+    <script type="module" crossorigin src="./assets/index-BmmNpJ_T.js"></script>
+    <link rel="stylesheet" crossorigin href="./assets/index-DVasXfKm.css">
   </head>
   </head>
   <script>
   <script>
     console.log(11111, editormd);
     console.log(11111, editormd);

BIN
static/index.html.gz


+ 14 - 0
tools/check.py

@@ -8,6 +8,7 @@ import win32com
 
 
 from config import base_path, file_type_map
 from config import base_path, file_type_map
 from tools.oss_client import MinioClient
 from tools.oss_client import MinioClient
+from tools.logger_handle import logger
 
 
 REPLY = {'html': None, 'cookies': None, 'token': None}
 REPLY = {'html': None, 'cookies': None, 'token': None}
 
 
@@ -70,3 +71,16 @@ def is_file_open_in_wps(file_path: str) -> bool:
     finally:
     finally:
         pythoncom.CoUninitialize()
         pythoncom.CoUninitialize()
     return False
     return False
+
+
+def enable_wps_login_force():
+    import winreg
+
+    key_path = r"Software\kingsoft\Office\6.0\plugins\officespace\flogin"
+    try:
+        key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, key_path, 0, winreg.KEY_SET_VALUE)
+        winreg.SetValueEx(key, "enableForceLoginForHasInstallDevice", 0, winreg.REG_SZ, "false")
+        winreg.CloseKey(key)
+        logger.info("WPS注册表:enableForceLoginForHasInstallDevice=false 修改成功")
+    except Exception as e:
+        logger.info(f"WPS注册表修改失败: {e}")

+ 1 - 1
tools/serve_client.py

@@ -263,5 +263,5 @@ class ServerClient:
 server_client = ServerClient(TARGET_URL, args['username'], args['password'])
 server_client = ServerClient(TARGET_URL, args['username'], args['password'])
 
 
 if __name__ == '__main__':
 if __name__ == '__main__':
-    res = ServerClient.decryption('97a66be9a6cc923c21dad3bbbd89274ddd414593f56da4681750df7dbdfd2f49fb68a49f4f8193edc33f4854f408aea083604e44da7696950f0fe1ef6a9bb23f82de304dfe921f2d300c79d5acacc687db4b8b34b6d714c9eff8bada97a38baa1eb5f11fd0f19f812a294f120e062c7091f0e28e8adc578e2433b6916d7d414a627bf772ff0cb0af718005a08f2c71dfdb05002000f6ca822c1099199967d7999f6678857f0f06ea68dc5ad390e71746662e2d73bba7e93784f93f5d29a7521d8165419153ff4edb9d79a76f4d92dfcb')
+    res = server_client.decryption('2d841ab7efaf14c93f56db87a5cbf8b5f523b9a0ad72459e7e4fc7b5a58ea91b3c7028db3a990854a01f9c978db2743aea21d6cbe96e618d83136c034a754b787df54e5629ae47858700c71f3021637c3a34f9bbc7e5cdc424b75dae6509ae6df9c7b5fb8f7c239a036b18a6ebf657c4446eaa339702b47e91709c263c6635910c7c27834450fb7e75e07d097752ba7ee1e2e21fb14031ddadb920480ba33fe33519a6b625878ddc372d72bd0b8172d5d1e91810dfae8cdf05032c36ef36f0bfa1872f39d6aaa08291ab8ed0ca708715392d8180cf42ed08e12f4ea2ceb648ad5dc98e7ff14b449b02e0b3d7e459eccf9561793ea852d9351155563be4990f629dffe0142224e15d68745ac32ac8bb8175cb88cc1d5dd0ead1c298582c4fa2dc7f60fb7befae37da5e71d14a5690e439b912a5ecce837677a298148450d0e2e93f2172d92cac83192cbfb5a370b9e43731da227b06d50ae535df2aef8e7009337b16ebd2bb7d3168c812a539e276d93c5497c6070f52c84c8deccee8b6f6bedf9902aac78426920ea46f2d70597e3ec9629c26f2349696a5ef7ab733e8c4988b555ffa8955f5217d5d7bbdd135b412d39a3b72ae6c795e734e438614741e6072')
     print('aaaa', res)
     print('aaaa', res)

+ 133 - 8
tools/wps_plugin_contorl.py

@@ -1,10 +1,102 @@
+import base64
 import json
 import json
+import os
+import subprocess
+import winreg
+from typing import Optional, Literal
 
 
-import base64
+import pythoncom
+import win32com
+from win32com.client import Dispatch
+from tools.logger_handle import logger
+
+WPSComponent = Literal['wps', 'et', 'wpp']  # wps=文字, et=表格, wpp=演示
+
+
+def get_wps_exe_path(component: WPSComponent = 'wps') -> Optional[str]:
+    """
+    获取 WPS 各组件的 exe 路径
+    :param component: 'wps' 文字, 'et' 表格, 'wpp' 演示
+    :return: 组件的绝对路径,未找到则返回 None
+    """
+    # 组件对应的可执行文件名
+    exe_map = {
+        'wps': 'wps.exe',
+        'et': 'et.exe',
+        'wpp': 'wpp.exe'
+    }
+    exe_name = exe_map.get(component)
+    if not exe_name:
+        return None
 
 
-from config import base_path
+    # WPS 在注册表中的可能路径(与之前相同)
+    wps_reg_paths = [
+        (winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Kingsoft\Office\6.0\common"),
+        (winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\WOW6432Node\Kingsoft\Office\6.0\common"),
+        (winreg.HKEY_CURRENT_USER, r"SOFTWARE\Kingsoft\Office\6.0\common")
+    ]
 
 
-print(base_path)
+    for hkey, subkey in wps_reg_paths:
+        try:
+            key = winreg.OpenKey(hkey, subkey, 0, winreg.KEY_READ)
+            install_root, _ = winreg.QueryValueEx(key, "InstallRoot")
+            winreg.CloseKey(key)
+            # 拼接 office6 目录下的组件 exe
+            component_path = os.path.join(install_root, "office6", exe_name)
+            if os.path.isfile(component_path):
+                return component_path
+        except (FileNotFoundError, OSError, WindowsError):
+            continue
+    return None
+
+def get_ms_office_exe_path(application: str) -> Optional[str]:
+    """
+    获取 Microsoft Office 组件路径
+    :param application: 'Word', 'Excel', 'PowerPoint'
+    """
+    app_map = {'Word': 'WINWORD.EXE', 'Excel': 'EXCEL.EXE', 'PowerPoint': 'POWERPNT.EXE'}
+    exe_name = app_map.get(application)
+    if not exe_name:
+        return None
+    # 遍历常见的 Office 版本号
+    office_versions = ['16.0', '15.0', '14.0', '12.0']
+    for version in office_versions:
+        reg_path = rf"SOFTWARE\Microsoft\Office\{version}\{application}\InstallRoot"
+        for hkey in [winreg.HKEY_LOCAL_MACHINE, winreg.HKEY_CURRENT_USER]:
+            try:
+                key = winreg.OpenKey(hkey, reg_path, 0, winreg.KEY_READ)
+                install_path, _ = winreg.QueryValueEx(key, "Path")
+                winreg.CloseKey(key)
+                exe_path = os.path.join(install_path, exe_name)
+                if os.path.isfile(exe_path):
+                    return exe_path
+            except (FileNotFoundError, OSError, WindowsError):
+                continue
+    return None
+
+
+def find_wps_via_registry():
+    """通过Windows注册表查找WPS的安装路径"""
+    possible_paths = [
+        (winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Kingsoft\Office\6.0\common"),
+        (winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\WOW6432Node\Kingsoft\Office\6.0\common"),
+        (winreg.HKEY_CURRENT_USER, r"SOFTWARE\Kingsoft\Office\6.0\common")
+    ]
+    for hkey, subkey in possible_paths:
+        try:
+            key = winreg.OpenKey(hkey, subkey, 0, winreg.KEY_READ | winreg.KEY_WOW64_64KEY)
+            install_path, _ = winreg.QueryValueEx(key, "InstallRoot")
+            winreg.CloseKey(key)
+            # 尝试找到主要的可执行文件
+            for exe in ["wps.exe", "WPSOffice.exe", "ksolaunch.exe"]:
+                full_path = os.path.join(install_path, "office6", exe)
+                if os.path.isfile(full_path):
+                    return full_path
+        except FileNotFoundError:
+            continue
+        except Exception:
+            continue
+    return None
 
 
 
 
 def encode_payload(payload):
 def encode_payload(payload):
@@ -12,10 +104,43 @@ def encode_payload(payload):
     return json.dumps(payload)
     return json.dumps(payload)
 
 
 
 
-payload = {'data': {"cmd": "enable", "name": "wpsPlugin", "url": "http://172.10.3.110:3000/", "addonType": "wps",
-                    "online": "true"},
-           'serverId': '524ef80e-7ccd-43af-9fcb-69f3b0269730'}
+def is_wps_available():
+    """检测当前系统是否安装了可用的 WPS Office (COM 接口)"""
+    progids_to_try = [
+        'KWPS.Application',   # WPS Office 2019+ 常用
+        'WPS.Application',    # 旧版本可能用
+        'Kingsoft.Application'
+    ]
+    for progid in progids_to_try:
+        try:
+            pythoncom.CoInitialize()
+            app = win32com.client.Dispatch(progid)
+            app.Quit()
+            del app
+            pythoncom.CoUninitialize()
+            logger.info(f"检测到可用的 WPS: {progid}")
+            return True
+        except Exception:
+            continue
+        finally:
+            try:
+                pythoncom.CoUninitialize()
+            except:
+                pass
+    logger.warning("未检测到可用的 WPS Office,请确认已安装并注册 COM 接口")
+    return False
+
 
 
-payload['data'] = base64.b64encode(json.dumps(payload['data']).encode('utf-8')).decode('utf-8')
+if __name__ == '__main__':
+    print(get_wps_exe_path('wps'))
+    print(get_wps_exe_path('et'))
+    print(get_wps_exe_path('wpp'))
+    print(subprocess.Popen([get_wps_exe_path('wps'), r"C:\Users\Lenovo\Desktop\dify本地部署.txt"]))
 
 
-print(payload)
+    # payload = {'data': {"cmd": "enable", "name": "wpsPlugin", "url": "http://172.10.3.110:3000/", "addonType": "wps",
+    #                     "online": "true"},
+    #            'serverId': '524ef80e-7ccd-43af-9fcb-69f3b0269730'}
+    #
+    # payload['data'] = base64.b64encode(json.dumps(payload['data']).encode('utf-8')).decode('utf-8')
+    #
+    # print(payload)

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff