混合开发浅析

混合开发本身已经不是一个新概念了,现在也有很多种方案,比如:Hybrid App,用原生和Web/html5来开发应用;Facebook的开源框架 React Native;阿里的开源框架 Weex和最简单的Web App。Web App 是指基于 Web 的应用,运行于网络和标准浏览器上,相当于一个网页然后加一个 App 的壳。本专栏主要讲的就是运用Vue 、 Node.js和Electron来实现Web App。他对前端开发人员是相当友好并且成本相对较低的。

写在前面

当我们在实际工作中有这种需求( 前端工程师开发客户端 )的时候,往往会考虑很多,并且,针对实际项目要求,还要解决一些特定的问题。所以,在一开始动手前,需要把一些东西想清楚。比如:做一个本地工具,就要考虑如何实时展现出日志,那么日志又分服务端日志和客户端日志,需要作不同的处理。如果是Web App的话,那么,很可能你是一个前端开发工程师,那有些地方就需要换一个思维去思考问题,例如:前端与后台通信只能通过发常规的Http请求吗?不是的,前端还可以与客户端通信,以Electron为例,可以通过 IpcRenderer模块实现前端与客户端之间的通信,然后客户端与后台通信,或者直接通过写本地文件进行交互。下面是简单的IpcRenderer实现样例:

/preload.js

// All of the Node.js APIs are available in the preload process.
// It has the same sandbox as a Chrome extension.
window.ipcRenderer = require('electron').ipcRenderer;

将ipcRenderer对象挂载到Window上,在客户端启动的时候通过webPreference配置去将它挂载到Window对象上:

/main.js

 // 这里省略了其他参数
 let mainWindow = new BrowserWindow({
        webPreferences: {
            preload: path.join(__dirname, 'preload.js')
        }
    });

官方的解释是:
在页面运行其他脚本之前预先加载指定的脚本 无论页面是否集成Node, 此脚本都可以访问所有Node API 脚本路径为文件的绝对路径。 当 node integration 关闭时, 预加载的脚本将从全局范围重新引入node的全局引用标志

设置了这些之后,就可以在前端去调用它,用它来发送/接收消息,实现通信的目的,具体如下:

/app.vue

const ipcRenderer = window.ipcRenderer;
// 发送消息
ipcRenderer.send('message', 'this is a message');
 ipcRenderer.on('receive', (message) => {
     this.message = message;
     // do something
 });

/main.js

// 客户端用IpcMain来接收消息
 ipcMain.on('message', async (event, msg)=>{
      // do something
      event.sender.send('receive', msg);
  })

整个过程,不需要发起任何Http请求,就能实现前端与客户端的通信,与常规的前端开发是不一样的。

主要架构

前面有提到,混合开发主要涉及到前端、服务端(后端)和客户端,与传统的前后端的架构有所不同,却又紧密相连,那要实现这样一个东西,它的架构是怎样的呢?别着急,请继续往下看。首先,要考虑的是,它们之间都能够”互相通气“,这里的通气就是指它们能够传输消息。前端与服务端是没有问题的,走Http请求就行了,前端与客户端,前面也讲了可以通过客户端的组件实现消息通信,那服务端与客户端呢?这里其实也是一样,如果是本地服务,可以直接通过写本地文件的形式通信,也可以发起Node.jsHttp请求,但是很多情况下,客户端与后台服务是很少的。其次,除了这些之外,还有其他相关的地方,比如,退出客户端需要关闭服务端的本地进程,避免内存占用,或者二次启动的时候报端口被占用的错误。最后,通过上面这些简单的分析,我们可以知道一个大概的架构,如下图:

在这里插入图片描述

上图中,日志文件是一个根据实际情况调整的选项,如果服务端是在远程服务器上,那么这里的本地文件就通过Http接口的形式发送。如果是本地的服务端就可以采用上面的方式去写本地文件。

值得注意的是,这里需要客户端去做一个心跳检测的事情,因为,客户端在这里只是一个壳子,有时候因为异常或者其他因素,服务挂了,需要能够自动拉起它,这个在本地客户端的情况尤其重要。

心跳检测

心跳检测,其实,可以在前端实现,也可以在客户端实现 。但是,前端实现依赖客户端,所以这里还是直接讲客户端实现吧。它的主要思路就是,每隔一段时间去探测一下后台服务是否还在活动。在这里,我们就讲一下本地服务的情况,因为它是最容易挂掉的了。

定时器

首先,就是很简单的一个定时器,写在客户端的Main.js中,它会随着客户端的退出而关闭,并且监听本地服务端的情况,具体如下:

/main.js

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(async () => {
    setInterval(() => {
        const live = checkServerStatus(port);
        if (!live){
            startServer()
        }
    }, 10000);
})

app Ready之后,就可以触发这个定时器了,因为在CreateWindow的时候已经去加载了界面了,这个时候服务正常情况下应该已经启动了,不然就会报错。在这里,checkServerStatus方法是如何实现的呢?别着急,请继续往下看。

判断服务是否启动

如何判断本地服务已经启动了,最直接的方法就是去判断端口是否已经被占用,如果正常访问,端口又被占用了,那说明服务是正常的,反之,服务挂了,需要重新启动。具体实现代码如下:

function portUsed(port){ // 判断端口是否被占用
    return new Promise((resolve)=>{
        let server = net.createServer().listen(port);
        server.on('listening',function(){
            server.close();
            resolve(port);
        });
        server.on('error',function(err){
            if(err.code == 'EADDRINUSE'){
                resolve(err);
            }
        });             
    });
}

async function checkServerStatus(port) {
    let res = await portUsed(port);
    if(res instanceof Error){ // 端口已启动,可以创建应用
        return true;
    } else{ // 启动本地Node.js服务
        return false;
    }
}

通过创建一个Node.js本地服务,监听它的error事件和listening事件,如果报错了,说明这个端口已经被占用了,不需要重新启动。否则,需要重新启动,并关掉之前启动的服务。这个代码参考了网上的实现,看起来没有毛病,但是实际跑起来会有问题,listening方法并不靠谱,有时候已经启动了某端口,再次调用这个方法的时候,还是会触发listening,导致误以为它没有启动,其实是已经启动了,最后报错。

那么,要如何优化它呢,尝试一番后,发现一个比较绕的方法,就是在Windows系统中去查找对应端口的信息,这样就可以直接判断它是否已经在运行了。具体实现如下:

async function portUsed(port) {
		// 相当于执行 netstat -aon 
    const ls = spawnSync('netstat', [`-aon`], {detached: true});
    const stdout = ls.stdout.toString();
    // const stdout = iconv.decode(Buffer.from(ls.stdout, "binary"), "GBK");
    const list = stdout.split('\n');
    for ( let processMessage of list) {
        let pms = processMessage.trim().split(/\s+/);
        if (process.platform === 'win32') {
            let address = pms[1];
            if (address && address.includes(`:${port}`)) {
                console.log('port is used.');
                return true;
            }
        } else { // mac
            // todo
        }
    }

    return false;
}

这里只实现了Windows版本的,Mac版本的其实原理与Windows下的是一样的。通过 执行netstat -aon命令来列出当前系统中的进程列表,然后,查找出与当前端口号匹配的记录。这个方法,比上一个版本实现的方法可靠多了,并且实战中很有效果。

最后,贴一下RunServer的代码,仅供参考:

const Koa = require('koa');
const convert = require('koa-convert');
const cors = require('koa-cors'); //跨域
const bodyParser = require('koa-bodyparser'); //请求体JSON解析
const onerror = require('koa-onerror'); //错误处理
const resource = require('koa-static');//静态资源托管
const { historyApiFallback } = require('koa2-connect-history-api-fallback'); 
const path = require('path');
const logger = require('../util/log4js');


const routes = require('./routes/index.js');
const config = require('../../config/index.js');

const app = new Koa();

const env = process.NODE_ENV || 'dev';

onerror(app);

app.use(historyApiFallback({ whiteList: ['/api'] }));

app.use(convert(cors()));

app.use(bodyParser());

app.use(resource(path.join(__dirname, '../../public')));

app.use(async (ctx, next) => {
    const start = new Date();
    await next();
    const ms = new Date() - start;
    logger.info(`${ctx.method} ${ctx.url} - ${ms}ms`);
});

// routes
app.use(routes.routes(), routes.allowedMethods());


app.on('error', (error, ctx) => {
    logger.info('other error ' + JSON.stringify(ctx.onerror));
    logger.info('server error:' + error);
});

app.listen(config[env].port).on('listening', function () {
    logger.info('listening port : ' + config[env].port);
});

它其实就是做了一个简单的Node.js服务做的事情,把前端打包成静态放到 public里去就可以跑起来了。

总结

本文简单阐述了混合开发Web App的架构和它的一些关键点,实际开发中遇到的问题比这个多的多,但是事情总是需要人去做的,没有解决不了的问题。希望看完本文对你有帮助。




共勉。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

洲上牧童

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值