后端 – 用于Excel宏的队列

huangapple go评论71阅读模式
英文:

Backend - Queue for Excel Macro

问题

我有一个关于找到正确架构和解决以下问题的大问题:

  1. 我有一个VBA宏,从一个工作表中检索参数,计算一个算法(需要时间),然后在另一个工作表中输出结果。
  2. 我需要创建一个后端(可以是Flask或Nodejs),它连接到某种队列(Redis或RabbitMQ),从前端(或HTTP客户端)获取参数作为POST请求,然后将这些参数保存到队列中。然后,一个工作函数应异步获取它们运行Excel宏并发送结果回来。
  3. 这不是强制性的,但我想使用Docker(我对其他建议开放)。

所以我尝试过使用xlwings、redis和Flask与Docker的解决方案,但它不起作用。

  • 如果我在Windows上使用Docker容器,我无法使用Redis。
  • 如果我在Linux上使用Docker容器,我无法使用pywin32,只能使用xlwings库,但不支持保存一些参数。 Xlwings的限制
  • 我可以向Excel添加另一个宏,以通过rabbitmq进行HTTP请求 如此所示,但我不知道如何在Docker容器中保持此宏运行。

你能给我一个更好的架构或解决方案吗?

谢谢。

# ----------------- Flask (main.py) -------------
import os
from flask import Flask, make_response, request, jsonify
from redis import Redis
from rq import Queue
from server.excel_worker import run_excel_macro

app = Flask(__name__)
redis = Redis('redis', 6379)
q = Queue('excel_tasks', connection=redis)

@app.route('/add-task', methods=['POST'])
def create_task():
    json = request.json
    absolute_path = os.path.dirname(__file__)
    filename = os.path.join('excel-file', 'excel-file.xlsm')
    full_path = os.path.join(absolute_path, filename)
    task = q.enqueue(run_excel_macro, full_path, 'Macro1', json)
    data = {'message': 'Task Created', 'task_id': task.get_id()}
    return make_response(jsonify(data), 201)

@app.route("/tasks/<task_id>", methods=["GET"])
def get_status(task_id):
    task = q.fetch_job(task_id)
    if task.is_queued:
        response_object = {
            "data": {
                "task_id": task.get_id(),
                "task_status": task.get_status(),
                "task_result": task.return_value(),
            },
        }
        return jsonify(response_object)
    if task.is_started:
        response_object = {
            "data": {
                "task_id": task.get_id(),
                "task_status": task.get_status(),
                "task_result": task.return_value(),
            },
        }
        return jsonify(response_object)
    elif task.is_finished:
        return jsonify({'result': task.return_value()})
    else:
        return jsonify({"status": "error"})

if __name__ == '__main__':
    app.run(host='0.0.0.0')

# ----------------- Excel Worker-------------
def run_excel_macro(filename, macro_name, variable_dict):
   # 设置一些来自变量的参数
    name = variable_dict['name']
    surname = variable_dict['surname']
    # 设置更多
    # 使用xlwings运行Excel宏
    xw.App().visible = False
    wb = xw.Book(filename)
    sheet1 = wb.sheets[0]
    # 在Excel工作表中写入一些值
     sheet1.range('A1').value = name
     sheet1.range('A2').value = surname
    macro = wb.macro(macro_name)
    # 运行宏
    macro()
     # 获取宏的计算并发送结果
    result = {
      ... 计算结果
    }
    wb.save()
    wb.close()
    return result

# -----------------Dockerfile-------------
FROM python:3.9
COPY . /app
WORKDIR /app
RUN pip install -r requirements.txt
EXPOSE 5000
CMD ["python", "main.py"]

# -----------------Docker-Compose-------------
version: '3.8'

services:
  redis:
    image: redis:latest
    ports:
      - '6379:6379'
    volumes:
      - ./redis:/redis

  flask-app:
    image: app-image
    container_name: flask-app
    restart: always
    build: app
    ports:
      - '5000:5000'
    depends_on:
      - redis

  worker:
    image: app-image:latest
    depends_on:
      - redis
    command: rq worker excel_tasks --url redis://redis:6379
    links:
      - redis
英文:

I have a really big problem about finding the right architecture and for the following;

  1. I have a VBA Macro which retrieves the parameters from one sheet, calculates an algorithm (it takes time) and outputs the result in the another sheet.
  2. I need to create a backend (could be Flask or Nodejs) which is connected a kind of queue (Redis or RabbitMQ) which gets the parameters from Frontend (or HTTP Client) as POST request, then saves this parameters into the queue. After that a worker function should get them asynchronously run the excel macro and sends result back.
  3. Its not obligatory but I want to use Docker ( I am open any other suggestions).

So I have tried this solution with xlwings, redis and flask with docker but it did not work.

  • If I use docker containers for Windows, I can not use Redis.
  • If I use docker containers for Linux, I can not use pywin32, I can only use xlwings library but it is not supported to save some parameters. Limitation of Xlwings
  • I can add the another macro to excel to make http request to rabbitmq As Shown here but I dont know how to keep this macro running in Docker container

Can you give me a better architecture or solution?

Thank you.

    # ----------------- Flask (main.py) -------------
import os
from flask import Flask, make_response, request, jsonify
from redis import Redis
from rq import Queue
from server.excel_worker import run_excel_macro
app = Flask(__name__)
redis = Redis(&#39;redis&#39;, 6379)
q = Queue(&#39;excel_tasks&#39;, connection=redis)
@app.route(&#39;/add-task&#39;, methods=[&#39;POST&#39;])
def create_task():
json = request.json
absolute_path = os.path.dirname(__file__)
filename = os.path.join(&#39;excel-file&#39;, &#39;excel-file.xlsm&#39;)
full_path = os.path.join(absolute_path, filename)
task = q.enqueue(run_excel_macro, full_path, &#39;Macro1&#39;, json)
data = {&#39;message&#39;: &#39;Task Created&#39;, &#39;task_id&#39;: task.get_id()}
return make_response(jsonify(data), 201)
@app.route(&quot;/tasks/&lt;task_id&gt;&quot;, methods=[&quot;GET&quot;])
def get_status(task_id):
task = q.fetch_job(task_id)
if task.is_queued:
response_object = {
&quot;data&quot;: {
&quot;task_id&quot;: task.get_id(),
&quot;task_status&quot;: task.get_status(),
&quot;task_result&quot;: task.return_value(),
},
}
return jsonify(response_object)
if task.is_started:
response_object = {
&quot;data&quot;: {
&quot;task_id&quot;: task.get_id(),
&quot;task_status&quot;: task.get_status(),
&quot;task_result&quot;: task.return_value(),
},
}
return jsonify(response_object)
elif task.is_finished:
return jsonify({&#39;result&#39;: task.return_value()})
else:
return jsonify({&quot;status&quot;: &quot;error&quot;})
if __name__ == &#39;__main__&#39;:
app.run(host=&#39;0.0.0.0&#39;)
# ----------------- Excel Worker-------------
def run_excel_macro(filename, macro_name, variable_dict):
# Setting some parameters from variables
name = variable_dict[&#39;name&#39;]
surname = variable_dict[&#39;surname&#39;]
# Set Some more
# Run Excel macro using xlwings
xw.App().visible = False
wb = xw.Book(filename)
sheet1 = wb.sheets[0]
# Write some values in Excel Sheet
sheet1.range(&#39;A1&#39;).value = name
sheet1.range(&#39;A2&#39;).value = surname
macro = wb.macro(macro_name)
# Run Macro 
macro()
# Get Calculation of Macro and send result
result = {
... calculation result
}
wb.save()
wb.close()
return result
# -----------------Dockerfile-------------
FROM python:3.9
COPY . /app
WORKDIR /app
RUN pip install -r requirements.txt
EXPOSE 5000
CMD [&quot;python&quot;, &quot;main.py&quot;]
# -----------------Docker-Compose-------------
version: &#39;3.8&#39;
services:
redis:
image: redis:latest
ports:
- &#39;6379:6379&#39;
volumes:
- ./redis:/redis
flask-app:
image: app-image
container_name: flask-app
restart: always
build: app
ports:
- &#39;5000:5000&#39;
depends_on:
- redis
worker:
image: app-image:latest
depends_on:
- redis
command: rq worker excel_tasks --url redis://redis:6379
links:
- redis

答案1

得分: 0

在虚拟机或容器中运行Excel可能不值得麻烦。如果您的代码在您的计算机上正常工作,并且不需要在服务器上运行,那么您可能可以在此停止。

此外,由于这是关于架构的问题,您不需要使用混合架构来进行POST请求,VBA已经具备这个能力。例如,您可以使用Microsoft WinHTTP Services版本5.1库中的WinHttp.WinHttpRequest.5.1对象。

例如:

    Dim TCRequestItem As Object
Set TCRequestItem = CreateObject("WinHttp.WinHttpRequest.5.1")
Dim Endpoint As String
Endpoint = "https://jsonplaceholder.typicode.com/posts"
Dim PayLoad As String
PayLoad = "{ ""context"" : { ""priority"" : ""REALTIME"", ""properties"" : [ ] }, ""inputPerson"" : { ""type"" : ""NaturalInputPerson"", ""personName"" : { ""nameFields"" : [ { ""string"" : ""John"", ""fieldType"" : ""GIVENNAME""}, {""string"" : ""Doe"", ""fieldType"" : ""SURNAME""} ]}, ""gender"" : ""UNKNOWN"" }}"
Dim Token As String
Token = "FakeToken"
With TCRequestItem
.Open "POST", Endpoint, False
.setRequestHeader "Content-Type", "application/json"
.setRequestHeader "Accept", "application/json"
.setRequestHeader "User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36"
.setRequestHeader "Authorization", "Bearer " & Token
.Send PayLoad
End With
Debug.Print TCRequestItem.responseText

您还可以拥有一个控制队列并进行POST请求的主工作簿(父工作簿),而另一个工作簿(子工作簿)则执行算法。

    Dim ExcelApp As Excel.Application
Set ExcelApp = CreateObject("Excel.Application")
ExcelApp.Workbooks.Open "YourWorkBookLocation"
ExcelApp.Run "MacroName"
ExcelApp.Quit
Set ExcelApp = Nothing

然后,子工作簿可以将数据写入文本文件,而父工作簿将在循环中等待它(确保使用Windows API的Sleep来降低CPU使用率)。

如果您的算法支持,甚至可以考虑使用多线程,通过创建并行运行的多个Excel实例来实现(还可以参考此Analyst's Cave文章)。

然而,如果您真的需要在服务器上运行这个,您可以考虑支付支持Windows的Web托管服务。否则,我建议将VBA代码移植到更容易在容器中运行的类似语言,如VBScript、VB6、TwinBASIC甚至VB.NET。

英文:

The trouble of running Excel inside a VM or a Container might not be worth it. If your code works well on your machine and it doesn't need to be on a server, you can probably stop here.

Also, since this is a question about architecture, you don't need to use a mixed architecture to make POST requests, VBA has that capability already. For instance, you could use the WinHttp.WinHttpRequest.5.1 object from the Microsoft WinHTTP Services, version 5.1 Library.

For Example:

    Dim TCRequestItem As Object
Set TCRequestItem = CreateObject(&quot;WinHttp.WinHttpRequest.5.1&quot;)
Dim Endpoint As String
Endpoint = &quot;https://jsonplaceholder.typicode.com/posts&quot;
Dim PayLoad As String
PayLoad = &quot;{ &quot;&quot;context&quot;&quot; : { &quot;&quot;priority&quot;&quot; : &quot;&quot;REALTIME&quot;&quot;, &quot;&quot;properties&quot;&quot; : [ ] }, &quot;&quot;inputPerson&quot;&quot; : { &quot;&quot;type&quot;&quot; : &quot;&quot;NaturalInputPerson&quot;&quot;, &quot;&quot;personName&quot;&quot; : { &quot;&quot;nameFields&quot;&quot; : [ { &quot;&quot;string&quot;&quot; : &quot;&quot;John&quot;&quot;, &quot;&quot;fieldType&quot;&quot; : &quot;&quot;GIVENNAME&quot;&quot;}, {&quot;&quot;string&quot;&quot; : &quot;&quot;Doe&quot;&quot;, &quot;&quot;fieldType&quot;&quot; : &quot;&quot;SURNAME&quot;&quot;} ]}, &quot;&quot;gender&quot;&quot; : &quot;&quot;UNKNOWN&quot;&quot; }}&quot;
Dim Token As String
Token = &quot;FakeToken&quot;
With TCRequestItem
.Open &quot;POST&quot;, Endpoint, False
.setRequestHeader &quot;Content-Type&quot;, &quot;application/json&quot;
.setRequestHeader &quot;Accept&quot;, &quot;application/json&quot;
.setRequestHeader &quot;User-Agent&quot;, &quot;Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36&quot;
.setRequestHeader &quot;Authorization&quot;, &quot;Bearer &quot; &amp; Token
.Send PayLoad
End With
Debug.Print TCRequestItem.responseText

You can also have a main workbook (parent) that controls the queue and make those POST requests while the other workbook (child) is executing the algorithm.

    Dim ExcelApp As Excel.Application
Set ExcelApp = CreateObject(&quot;Excel.Application&quot;)
ExcelApp.Workbooks.Open &quot;YourWorkBookLocation&quot;
ExcelApp.Run &quot;MacroName&quot;
ExcelApp.Quit
Set ExcelApp = Nothing

The child could then write to a text file and the parent would be waiting for it in a loop (make sure to use Sleep for the Windows API to reduce CPU usage).

You could even consider using multi-threading if your algorithm supports that by creating multiple instances of Excel that run in parallel. (See also this Analyst's Cave article on the topic).

<hr>

However, if you really need to run this on a server you could consider paying for a web-hosting service that supports Windows. Otherwise, I would recommend porting the VBA code to a similar language that can more easily be run in a container like VBScript, VB6, TwinBASIC or even VB.NET.

huangapple
  • 本文由 发表于 2023年6月22日 18:37:26
  • 转载请务必保留本文链接:https://go.coder-hub.com/76531034.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定