| 
 | 
 
每个程序员都会想拥有属于自己的个人博客,网络就像一片广阔无边的星空,如果其中有一颗属于自己的星球是多令人向往的事情。 
当然我也不例外,根据个人喜好模仿一家专门做设计的网站,也创建一个我自己的星球。 
 
  
部署完自己的博客肯定就会想马上分享给自己的朋友,又急冲冲在租了一个云服务器,搭建一个简单的服务器。 
在朋友分享(炫耀)后,觉得博客有点空洞,没想好怎么改造我的星球。 
因为我本身是做桌面端应用,自然就想到可不可以把我的博客变成一个独立的应用,很快在万能的知乎上找到一个Electron框架符合我的需求。 
直接打开React项目,打开electron官网,参考官网的快速安装 
yarn add electron在package.json里面添加执行入口和启动命令 
  "main": "main.js", 
  "homepage": "./", 
 
  "scripts": { 
    "start:main": "electron .", 
    "start:render": "react-scripts start", 
  },添加main.js内容,原生electron外框太丑,使用了自定义的标题栏 
const { app, BrowserWindow, ipcMain } = require('electron') 
const path = require('path') 
const url = require('url'); 
 
const createWindow = () => { 
    const win = new BrowserWindow({ 
        width: 1920, 
        height: 1080, 
        frame: false, 
        webPreferences: { 
            nodeIntegration: true, 
            contextIsolation: false 
        } 
    }) 
 
    win.loadURL('http://localhost:3000'); 
    // win.webContents.openDevTools() 
 
    ipcMain.on('min', () => { 
        win.minimize() 
    }) 
 
    ipcMain.on('max', () => { 
        if (win.isMaximized()) { 
            win.restore() 
        } else { 
            win.maximize() 
        } 
    }) 
 
    ipcMain.on('close', () => { 
        win.close() 
    }) 
} 
 
app.whenReady().then(() => { 
    createWindow() 
    app.on('window-all-closed', () => { 
        if (process.platform !== 'darwin') { 
            app.quit() 
        } 
    }) 
}) 
在React的标题栏添加拖拽区和最大最小关闭按钮 
const {ipcRenderer} = window.require('electron') 
 
<button className={Style.headerMin} onClick={() => ipcRenderer.send(&#34;min&#34;)} /> 
<button className={Style.headerMax} onClick={() => ipcRenderer.send(&#34;max&#34;)} /> 
<button className={Style.headerClose} onClick={() => ipcRenderer.send(&#34;close&#34;)} />对应css里面拖拽控件添加这个样式属性 
-webkit-app-region: drag;直接运行一下看看效果如何 
  
electron - 个人博客 
https://www.zhihu.com/video/1580938563797053441 
<hr/>前端界面实现了,但是作为一款桌面级应用,后端也是必须同步实现的。 
之前服务端是用flask搭建的,走的都是http协议,很快我就发现前后通信问题。 
没办法高效双向通信,http的通信模式是一种请求式单向通信,没办法满足应用层的MVC、MVP等等常见架构。 
后来通过做前端朋友推荐websocket全双工通信可以解决,通过建立长连接特性,向上封装一层通信接口请求、订阅、查询接口。 
from flask import Flask 
from flask_sockets import Sockets 
from geventwebsocket.handler import WebSocketHandler 
from geventwebsocket.server import WSGIServer 
from geventwebsocket.websocket import WebSocketError 
 
app = Flask(__name__) 
sockets = Sockets(app) 
 
publisher_socket_dict={} 
subscription_socket_dict={} 
 
@sockets.route(&#39;/publisher/<user_name>&#39;) 
def publisher_socket(user_socket, user_name): 
   publisher_socket_dict[user_name]=user_socket 
   print(&#39;publisher 新增socket:&#39;, user_name) 
 
   while True: 
      try: 
         user_msg = user_socket.receive() 
         if user_msg != None: 
            for u_name,u_socket in subscription_socket_dict.items(): 
               try: 
                  u_socket.send(user_msg) 
                  print(&#39;publisher 发送:&#39;, u_name, user_msg) 
               except WebSocketError as error: 
                  subscription_socket_dict.pop(u_name) 
                  print(&#39;subscription 删除socket:&#39;, u_name, error) 
      except WebSocketError as error: 
         publisher_socket_dict.pop(user_name) 
         print(&#39;publisher 删除socket:&#39;, user_name, error) 
 
@sockets.route(&#39;/subscription/<user_name>&#39;) 
def subscription_socket(user_socket, user_name): 
   subscription_socket_dict[user_name]=user_socket 
   print(&#39;subscription 新增socket:&#39;, user_name) 
 
web_gui_socket_dict={} 
@sockets.route(&#39;/message/<user_name>&#39;) 
def message_socket(user_socket, user_name): 
   web_gui_socket_dict[user_name]=user_socket 
   print(&#39;message 新增socket:&#39;, user_name) 
 
   while True: 
      try: 
         user_msg = user_socket.receive() 
         if user_msg != None: 
            for u_name,u_socket in web_gui_socket_dict.items(): 
                  if user_name == &#39;web&#39;: 
                     if u_name == &#39;gui&#39;: 
                        u_socket.send(user_msg) 
                        print(&#39;message web->gui 发送:&#39;, user_msg) 
                  elif user_name == &#39;gui&#39;: 
                     if u_name == &#39;web&#39;: 
                        u_socket.send(user_msg) 
                        print(&#39;message gui->web 发送:&#39;, user_msg) 
      except WebSocketError as error: 
         web_gui_socket_dict.pop(user_name) 
         print(&#39;message 删除socket:&#39;, user_name, error) 
 
http_server = WSGIServer((&#39;0.0.0.0&#39;, 9090), application=app, handler_class=WebSocketHandler) 
http_server.serve_forever()简单测试一下,用websocket代替flask做通信服务,react是可以和服务器正常通信。 
<hr/>一个大胆的想法就冒出脑海,如果我之前一直是用Qt写桌面端应用,是不是可以在曾经写过的Qt项目里面添加一个websocket微服务。 
因为我很多项目都是采用视图和数据分离的框架,那是不是可以用react来作为视图端的平替。 
这样通过electron框架使用web前端好看的界面风格,可以快速还原ui图的动画效果,还有可以借用庞大的前端社区来丰富生态库。 
而且同时简单搭个websocket服务器,屏蔽elelctron接口,又可以快速生产出web上位机。 
而作为后端我可以使用最熟练的C++作为开发,可以接入各种串口通信,跟硬件通信无缝衔接,也可以调用window原生接口。 
void ElecWebSocket::initServer(const QString &ip, int port) 
{ 
    m_webSocketServer = new QWebSocketServer(ip, QWebSocketServer::NonSecureMode); 
    m_webSocketServer->listen(QHostAddress::AnyIPv4, port); 
    connect(m_webSocketServer, &QWebSocketServer::newConnection, this, &QPhotoWebSocket::onSocketConnection); 
} 
 
void ElecWebSocket::onSocketConnection() 
{ 
    if(m_webSocketServer->hasPendingConnections()) 
    { 
        auto socket = m_webSocketServer->nextPendingConnection(); 
        connect(socket, &QWebSocket::textMessageReceived, this, &ElecWebSocket::onSocketMsg); 
        connect(socket, &QWebSocket::disconnected, this, &ElecWebSocket::onSocketDisconnected); 
        m_webSocketClient.append(socket); 
    } 
} 
 
void ElecWebSocket::onSocketMsg(const QString &data) 
{ 
    qDebug() << &#34;onSocketMsg&#34; << data; 
    QJsonDocument doc = QJsonDocument::fromJson(data.toLatin1()); 
    QJsonObject obj = doc.object(); 
    if(obj.contains(&#34;event&#34;)) 
    { 
        if(obj[&#34;event&#34;].toString() == &#34;onHandleEvent&#34;) 
        { 
            onHandleEvent(obj); 
        } 
    } 
} 
 
void ElecWebSocket::onSocketDisconnected() 
{ 
    auto socket = qobject_cast<QWebSocket*>(this->sender()); 
    m_webSocketClient.removeOne(socket); 
} 
 
void ElecWebSocket::send(const QJsonObject &data) 
{ 
    QJsonDocument doc; 
    doc.setObject(data); 
    for(QWebSocket* socket : m_webSocketClient) 
    { 
        if(socket->isValid()) 
        { 
            socket->sendBinaryMessage(doc.toJson(QJsonDocument::Compact)); 
        } 
        else { 
            qDebug() << &#34;error websocket:&#34; << socket->errorString(); 
            m_webSocketClient.removeOne(socket); 
        } 
    } 
} 
<hr/>开心的使用了一段时间,我又发现一些弊端。 
最大的一个问题就是,性能,性能,还是性能。 
因为我是做3d方向,涉及到对三维模块的显示和编辑,之前用vtk用得那一套处理方式,平替到three.js上面。 
研究了一段时间发现,three.js适合做显示,但对应模型编辑处理上面提供接口太少,然后想到一个曲折得办法,通过vtk先处理,然后把数据结果刷新到three.js上面。 
  const updateModel = () => { 
    if (stateGeometry !== undefined) 
    { 
      const positions = stateMesh.geometry.attributes.position.array 
      for(let i = 0; i < positions.length & i < stateGeometry.length; i++) 
      { 
        positions = stateGeometry / 1000; 
      } 
      stateMesh.geometry.attributes.position.needsUpdate = true 
    } 
  } 
然后就遇到头疼的问题,不知道是不是我刷新代码有问题,还是web渲染性能问题,出现卡顿情况。 
websocket传输也受到挑战,对于大数据量吞吐也出现延迟。 
现在还在办法优化中。。。 
还有一个问题就是安装包太大了,electron背锅,看到打完程序包,一个G左右的大小,头疼。 |   
 
 
 
 |