React Electron Node SQLite 构建EXE桌面应用

ldy 8月前 ⋅ 568 阅读

React Electron Node SQLite构建EXE桌面应用

背景

经典的软件架构分为BS(Browser/Server)架构和CS(Client/Server)架构。简单地说:我们常见的WPS、Photoshop、记事本等在计算机桌面打开的EXE程序就是CS架构的一种,CS架构的特点就是必须要有一个客户端程序在计算机上,而需要我们使用浏览器上网才能够访问的程序被称为BS架构。开发EXE桌面应用的高级语言有很多,我们熟知的有C、C++、C#、java等,但是他们有的构建UI视图困难,有点平台移植能力和兼容能力差,有点因为其面向过程的开发原则门槛高,让程序员和一些小企业增加了产品生产的成本。

随着nodeJs本身能力的逐步成长和社区不断地丰富和完善,利用JavaScript开发EXE程序变成了 可能,Electron就是这样的一个产物。我们可以访问Electron官网,探索一下这个有趣的框架:https://www.electronjs.org/

简介

“使用 JavaScript,HTML 和 CSS 构建跨平台的桌面应用程序”。Electron是由Github社区维护和开发,社区和参考资料非常丰富。建议同学们可以科学上网,在国外的技术网站可以学习到很多有用的技术和知识。

我们的目标是基于Electron构建一个本地服务的单机应用,当然我们会采取面向对象的方式和MVC思想进行开发。本地的服务端我们采取nodeJs的一个web框架express(http://expressjs.com/ ),数据的持久化使用SQLite3来进行保存数据(在构建大型应用时你当然可以使用MySQL、MongoDB等数据库),前端的UI框架我们使用React(https://react.docschina.org/ ),项目模块的管理使用webpack(虽然react官方脚手架已经做好了一切,但是我们有必要知道我们的“孩子”做了什么)。虽然是单机应用,但是熟悉服务端开发的同学应该可以很简单地利用网络通信将本地的服务放迁移到远程。

[^CS开发本地程序应该是没有服务端概念的,但是为了照顾java同学的迁移,叫做“服务端”其实不伤大雅]:

SWTO

客观地进行SWTO分析

electron模式传统模式(C、C++、C#、java)
strengths构建UI快捷、精美
社区和类库丰富
跨平台,一次编写处处使用
学习成本低
性能好,C系列语言直接接触硬件
安全性高,对于安全至上的软件为首选
灵活度高,应用层级所有的行为可以自主掌握
weaknesses性能较低,相较于传统模式electron采取了桥接模式的整体设计
网络资源不稳定,没有稳定的科学上网方法,打包失败率高
应用体积大,冗余多,简单地开发一个记事本软件也要将近80M磁盘空间(最近有一个mini-electron技术好像解决了该问题,https://zhuanlan.zhihu.com/p/40658674 )
不支持Windows XP系统,政府部门依旧有使用XP的情况,遗憾的是electron只支持到Windows7
UI构建极其困难,极其不灵活
C#等存在平台问题
社区少,资料旧(我做了一个调研,有的语言构建桌面程序的文档大多在五六年前甚至于十年前就没有更新)
学习成本高,C、C++之类的成本很高,即使java、C#相较于JavaScript也高出很多。
opportunities基于Chrome V8引擎,生态链逐渐丰富,同时electron提供了JavaScript遥不可及的能力:访问计算机客户端资源(浏览器如果没有限制地访问计算机本地资源会有很大安全威胁)大型应用,特殊计算机平台软件首选
threats前端演变速度和进化速度很快,electron本身成熟度有待提升新、轻应用,需要现代用户界面的应用开发难度大

传统桌面开发的框架如QT,javaFx之类的介绍:https://www.zhihu.com/question/264999651

Get Start

1、安装Electron依赖(全局)

npm install electron -g

这样安装可以使得electron全局可用,当然你也可以仅仅在项目中使用 最终的参数是 --save

2、利用官方脚手架进行初始化

其实这个官方脚手架基本上没做事情,只是定义了一个基本的骨架结构,我们完全可以自己搭建

# Clone the Quick Start repository 我们Git一下这个快速开始的工程
$ git clone https://github.com/electron/electron-quick-start
# Go into the repository
$ cd electron-quick-start
# Install the dependencies and run 一路默认安装就好
$ npm install && npm start

当最后一步npm start过后你就会看到你的桌面产生了一个标记着hello world的EXE程序。

看起来整个项目关于electron的知识就结束了(远不止这些,核心卖点其实在于主进程和渲染进程),它仿佛是仅仅提供了一个视窗的渲染界面,它做的这件事情其他语言也能做(这个我也可以很容易地从Python QT中得到)。但是我需要一个后端nodeJS服务器,一旦我启动应用程序,我应该能够启动nodeJS服务器进行后端处理,并启动Electron图形用户界面处理用户交互,类似于QT和Flask/Django做的事情一样。

所以这个时候NodeJS的服务端框架Express来了,用它你可以轻松设计出一个REST API服务端。

3、利用服务端设计REST API

这个时候我们已经在electron-quick-start这一级目录下面,我们安装express

npm install express -D

安装好了依赖,我们可以进行服务端的基本编写。java web的三层架构依旧有效地在这里组织管理我们的代码。我们在src下面新建一个server文件夹,当然你可以叫任何你喜欢的如api。在新建的文件夹下面创建index.js

let express = require('express');

const app = express();
const fs = require('fs');
//访问服务端的静态资源
const path = require('path');
app.use(express.static(path.join(__dirname, 'dist')));

//使用中间件,定义自己的路由,就像定义一个RequestMapping路径一般
app.use('/myTest',require('./router/[具体的路由]'));
app.use([这里做一切你想做的事情])

let server = app.listen(3000, function () {
    console.log('Express server listening on port ' + server.address().port);
});
module.exports = app;

写过Springboot的同学可以这样理解:我定义的app就如同我在Spring环境中定义的Context上下文容器一样,这个index.js如同我们的SpringBootApplication启动类,你可以在这里完成你想要的一切前置工作,不过一切的前置工作都要放在use的中间件操作中来,有必要的话你甚至于可以用JavaScript来实现你的IoC和AOP。我们这里设置的服务端端口号是3000,在npm上面找到SQLite3的依赖,将它安装好(步骤省略),就这样一个web服务器搭建完毕!

4、将electron和express粘合在一起

回到我们利用electron脚手架创建的那个初始化工程中,在src平级目录下有一个main.js的程序入口文件。我们需要对它进行一个自定义

const {app, BrowserWindow} = require('electron');
let mainWindow;
//项目中使用es6的语法,用babel转码
require('babel-register');
//这里特别关键,将express定义的app初始化,保证在主线程开启后我们的服务端就开启
//如果服务端依赖很多,启动时间比较久这里还可以写一个 async await 这样解决异步问题的函数,保证服务端加载完成之后再初始化electron
//如果没有Babel,当然你也可以异步加载,写一个心跳包测试。
require("./src/server/index");

//这里是在初始化exe应用,具体设置很多,官网可看
function createWindow() {
    mainWindow = new BrowserWindow({
        width: 1400,
        height: 800,
        backgroundColor: '#2e2c29',
        show: false,
        webPreferences: {
            nodeIntegration: true
        }
    });
    mainWindow.on('ready-to-show', function () {
        mainWindow.show();
    });
    //这里加载的方式有三种,一是直接加载静态文件,二是访问服务端,三是直接放一个URI定位符
    // mainWindow.loadFile('index.html');
    mainWindow.loadURL("http://localhost:3000");
    //mainWindow.loadURL("http://localhost:4000");
    //mainWindow.webContents.openDevTools();
    mainWindow.on('closed', function () {
        mainWindow = null
    })
}

app.on('ready', createWindow);
app.on('window-all-closed', function () {
    if (process.platform !== 'darwin') app.quit()
});
app.on('activate', function () {
    if (mainWindow === null) createWindow()
});

我选择了重点,非重点真的是非重点。
还有一个非常重要的事情,index.html 里面有一个非常重要的事情:安全策略要关掉!!!一般Chrome会自动检查。

5、请出React

react让我非常喜欢,觉得很编写应用很轻松。不像jQuery那样,我一直稀里糊涂。但是,你还在用Facebook官方的脚手架吗?为了少做一点事情,我们选择阿里的dvaJs https://dvajs.com/guide/getting-started.html#%E5%AE%89%E8%A3%85-dva-cli 这里是它的官网

注意了,我们为了代码结构的简洁在src下面,新建了一个client目录,将dva的文件路径放在client里面,你当然可以不用这样做,如果你觉得代码乱乱的不会影响你和你伙伴的心情的话,你可以使用任何你喜欢的方式管理你的代码。

等等,怎么做着做着就像web的前后端分离了?是的,让整个架构想web开发,这就是我的目的!所以在初始化dva之后记得在webpack配置一下代理

proxy: {
        '/**/*.do': {
            target: 'http://localhost:3000/'
        },
        '/**/upload': {
            target: 'localhost:3000/'
        },
    }

6、捋一捋

好了,大概会有点晕了。我们回顾一下我们做了啥?简单说,我们搭建了一个Express服务器,给我们提供REST API。我们还干了一件事是创建了React程序,通过API可以展示视图。Electron就像一个容器、一个盒子将两者绑在一起,让他们在里面和谐共处。难道Electron就像一个web容器(Tomcat之类的吗?),嗯嗯,我的想法是这样。
所以在开发环境下我们要启动两个命令:react、electron,express会在electron启动的时候加载。在生产环境下,我们会将react编译打包,放在express的静态资源下面。这样就保证我们应用一旦启动,所有的服务都能够各司其职。这个项目的文件目录结构和package.json(部分)的配置如下:

"scripts": {
   //服务端
    "start-backend": "electron .",
    //前端启动
    "start-frontend": "自定义的React启动命令",
    "react-build": "自定义的react编译",
    "clear": "rimraf node_modules",
    //生产环境的应用程序打包
    "package": "electron-builder --win --x64",
    "postinstall": "install-app-deps"
  },

{1FEF95A4-9DBD-45F8-8F7F-E9B927CF0BC5}_20200308204547.jpg

7、关于其他的设置

官网上有许多设置,大小、颜色、菜单栏、通知消息等等。react desktop是一款专门开发Windows系列和mac系列的UI库,开发出来的程序和原生系统的界面几乎一模一样!

8、服务端小福利

写惯了java的同学,数据持久化一般会使用mybatis或者hibernate这样的框架,无论是自己写SQL还是系统帮忙,都能得心应手,我在公司业务开发的时候在npm上面也找到了一个这样的库,推荐给大家:https://www.npmjs.com/package/mybatis-mapper 它叫mybatis-mapper,写出来的代码基本上长这样:

const batchInsert = function (param) {
    const sql = mybatisMapper.getStatement(MAPPER_NAME, 'batchInsertSchools', {schools: param}, CONS.MAPPER_FORMAT);
    db.insertWithCommit(sql);
};
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="schoolMapper">
    <insert id="batchInsertSchools">
        INSERT INTO T_ZCXZG_PW_SCHOOL
        (SCHOOL_ID, SCHOOL_NAME, CREATE_TIME) VALUES
        <foreach collection="schools" separator="," item="school">
            (#{school.schoolId},#{school.schoolName},#{school.createTime})
        </foreach>
    </insert>
</mapper>

最后

总体来说electron的探索还是很有意思的,还有就是第一次写技术博客,表达水平应该还有待提升。因为整个程序用了很多公司的库和资源,所以没办法将源代码贡献出来,但是后面有机会我会做一个基于官方原始的脚手架上传到网站。希望大家可以留言!


全部评论: 0

    我有话说: