serverless是近几年比较热门的一个概念,也是大前端发展的一个重要方向。serverless的兴起已经有一段时间了,早在几年前微信就推出了微信小程序云开发功能,其无需搭建服务器,只需使用平台提供的各项能力,即可快速开发业务。同时提供云数据库、云存储、云函数等功能,大大降低了开发者的开发成本,深受开发者喜爱。就在去年uni-app也推出了自家的serverless服务——uniCloud。
uniCloud是DCloud在阿里云和腾讯云的serverless服务上封装而成的。
它包含IaaS层(由阿里云和腾讯云提供硬件和网络)和PaaS层(由DCloud提供开发环境)。————————— uniClound官网
uniClound相对于其它云开发产品我认为有以下几点优势:
云函数默认是只有自己的app在前端通过uniCloud.callFunction来调用的,不会暴露到外网。一旦URL化后,开发者需要关注业务和资源安全。
云函数URL化 是 uniCloud 为开发者提供的 HTTP 访问服务,让开发者可以通过 HTTP URL 方式访问到云函数。
场景1:比如App端微信支付,需要配服务器回调地址,此时需要一个HTTP URL。
场景2:非uni-app开发的系统,想要连接uniCloud,读取数据,也需要通过HTTP URL方式访问。
下面本文就将基于uniClound从零开始搭建一个前端日志监控系统。
本文的主要目的是介绍serverless和上手uniClound实战,重点在于日志的采集和展示。为了简化系统日志数据主要来源于两方面:一是Vue的全局错误捕获,二是来自请求响应拦截器拦截的后端API请求错误。这个系统的简单示意如下:
根据vue官方文档介绍,Vue全局错误的捕获只需要配置Vue.config.errorHandler即可,这里为了使我们的日志监控系统更加完善和通用除了Vue的错误信息我们还需要采集错误发生的时间time
(uniCloud有个时区差,时间建议使用时间戳表示),错误发生的项目名project
。Vue全局错误捕获方法实现如下,其中addVueLog即是我们要通过云函数实现的API接口,关于接口的实现放在后面介绍。
// my-vatchvueerror.js
/****************************************************
* @description 捕获Vue全局错误
* @param {*} err 异常错误
* @param {*} vm 页面示例
* @param {*} info 错误说明
* @return {*}
* @author mingyong.g
****************************************************/
export default function(err, vm, info) {
const route = (vm.$page && vm.$page.route) || (vm.$mp && vm.$mp.page.route); // 获取uni-app项目的页面路由
let log = { // 日志对象
err: err.toString(),
info,
route,
time: new Date().getTime(),
project:"test"
};
addVueLog(log); // 新增日志的接口
}
在main.js
中配置错误捕获函数
// main.js
import catchVueError from "../my-vatchvueerror";
Vue.config.errorHandler = catchVueError;
这里以axios
的响应拦截器为例进行说明。关于API错误日志,我们需要关心如下几个信息:
下面的代码是axios响应拦截器的简单实现,其中addApiLog即是我们要通过云函数实现的API接口,关于接口的实现放在后面介绍。这里为了方面直接将包含请求参数的response.config
和包含响应数据的response.data
作为参数传入,其它公共信息放在接口内部实现。
// 响应拦截
service.interceptors.response.use(
(response) => {
let data = response.data;
/*
* 此处如果后台响应体中字段Msg = "ok" 则认为接口响应有效,否则视为错误响应
* 注意:这部分逻辑需根据业务和后端接口规范适当调整
*/
if (data.Msg == "ok" ) {
return data;
} else {
addApiLog(response.config, data); // 日志采集接口
return Promise.reject(data);
}
},
(err) => {
let errMsg = "";
if (err && err.response.status) {
switch (err.response.status) {
case 401:
errMsg = "登录状态失效,请重新登录";
router.push("/login");
break;
case 403:
errMsg = "拒绝访问";
break;
case 408:
errMsg = "请求超时";
break;
case 500:
errMsg = "服务器内部错误";
break;
case 501:
errMsg = "服务未实现";
break;
case 502:
errMsg = "网关错误";
break;
case 503:
errMsg = "服务不可用";
break;
case 504:
errMsg = "网关超时";
break;
case 505:
errMsg = "HTTP版本不受支持";
break;
default:
errMsg = err;
break;
}
} else {
errMsg = err;
}
addApiLog(err.config, { statusCode: err.response.status, Msg: err.response.data }); // 日志采集接口
return Promise.reject(errMsg);
}
);
为了简化开发工作uniCloud提供了基于uni-app 、 uni-ui 和uniCloud 的应用后台管理框架。
uniCloud admin 功能介绍
- 它基于 uni-app 的宽屏适配,可自动适配 PC 宽屏和手机各端,
- 它基于 uniCloud,是 serverless 的云开发。
- 它基于 uni-id,使用 uni-id 的用户账户、角色、权限系统。
——————————uniCloud官网
uniCloud界面
根据官方的使用教程,首先在HBuilderX 3.0+版本新建 uni-app 项目,选择 uniCloud admin 项目模板.
创建完成后,可以跟随云服务空间初始化向导初始化项目,创建并绑定云服务空间
运行
登录uniCloud控制台:https://unicloud.dcloud.net.cn/找到上述步骤3创建的云服务空间,这里我创建的服务空间是gmyl
点击详情进入云服务空间,可以看到uniCloud admin 默认已经为我们创建了以下云数据表:
6. opendb-admin-menus : 左侧菜单树管理表
7. opendb-verify-codes :验证码记录表
8. uni-id-log :uniCloud 的登录日志
9. uni-id-log :权限表
10. uni-id-roles : 角色配置表
11. uni-id-users :账号表
uniCloud admin提供了一套完整的后台管理方案,我们的目的在于搭建一个简单的日志监控系统,部分功能暂时还用不到这里现在uniCloud admin 注意是考虑到应用的扩展性。言归正题,除了上述框架自带的数据表以外我们还需要自行创建一张数据表用来存放日志数据,这里我为了区分分别建了两张表分别存放Vue日志和API日志。
{
"bsonType": "object",
"required": [],
"permission": {
"read": false,
"create": false,
"update": false,
"delete": false
},
"properties": {
"_id": {
"description": "ID,系统自动生成"
},
"project": {
"bsonType": "onject",
"description": "项目名称",
"trim": "both"
},
"url": {
"bsonType": "onject",
"description": "页面路由信息",
"trim": "both"
},
"errmsg": {
"bsonType": "onject",
"description": "错误描述",
"trim": "both"
},
"errtype": {
"bsonType": "string",
"description": "错误类型",
"trim": "both"
},
"occurrence_timestamp": {
"bsonType": "timestamp",
"description": "问题发生时间"
},
"state": {
"bsonType": "int",
"description": "0 待处理 1:已处理 ",
"trim": "both"
},
"handle_timestamp": {
"bsonType": "timestamp",
"description": "问题修复时间"
},
"reason": {
"bsonType": "string",
"description": "问题原因",
"trim": "both"
},
"solution": {
"bsonType": "string",
"description": "解决办法",
"trim": "both"
}
}
}
回到HbuilderX找到刚才创建的项目,依次展开uniCloud >> cloudfunctions , 右键cloudfunctions 点击新建云函数命名 为addVueLog
一个初始云函数结构如下,其中前端传递参数通过 event.body
获取。接下来的主要任务就是将前端传递的日志对象存储到云数据库中。云函数操作云数据库的教程可自行查阅官方文档:https://uniapp.dcloud.io/uniCloud/cf-database,此处不再赘述。
// 初始云函数
'use strict';
exports.main = async (event, context) => {
//event为客户端上传的参数
console.log('event : ', event)
//返回数据给客户端
return event
};
// 将数据写入云数据库
'use strict';
const db = uniCloud.database();
exports.main = async (event, context) => {
//event为客户端上传的参数
let data = event.body ? JSON.parse(event.body) : event;
if (event.project == "" && !event.body) { // 判断数据是否有效
return {
Msg: "Invalid Data!",
Data: "",
Count: 0
}
} else {
const dbCmd = db.command
const $ = dbCmd.aggregate
let res = await db.collection('vuelog_db').add(data) // 向表vuelog_db插入一条数据
//返回数据给客户端
return {
Data: "",
Msg: "ok",
Count: 0
}
}
};
开启云函数url化之前首先上传并部署云函数,找到对应的云函数右键上传部署。
上传成功可在uniCloud控制台的云函数列表中找到刚才上传的云函数。
在postman等API调试工具中,测试add_vuelog
API ,测试过程就不再演示了,云函数调用成功,云数据库中将新增一条记录。
![在这里插入图片描述](https://img-blog.csdnimg.cn/2021070513392277.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8
addApiLog的实现方式同上。至此一个日志系统的数据采集、存储功能就已经实现了。
数据展示部分虽然重要,但却不是本次开发的重点,这里直接使用uniCloud提供的schema2code (HBuilderX 3.1.0+ 支持)功能生成数据列表页面。
<view class="uni-container">
<unicloud-db
ref="udb"
@load="onqueryload"
collection="vuelog_db"
:options="options"
:where="where"
field="project,url,errmsg,errtype,occurrence_timestamp,state,handle_timestamp,reason,solution"
page-data="replace"
:orderby="orderby"
:getcount="true"
:page-size="options.pageSize"
:page-current="options.pageCurrent"
v-slot:default="{ data, pagination, loading, error }"
>
<uni-table
:loading="loading"
:emptyText="error.message || '没有更多数据'"
border
stripe
type="selection"
@selection-change="selectionChange"
>
<uni-tr>
<uni-th align="center">项目uni-th>
<uni-th align="center">页面路由uni-th>
<uni-th align="center">错误描述uni-th>
<uni-th align="center">错误类型uni-th>
<uni-th align="center">原因uni-th>
<uni-th align="center">解决方案uni-th>
<uni-th align="center">发生时间uni-th>
<uni-th align="center">修复时间uni-th>
<uni-th align="center">状态uni-th>
<uni-th align="center">操作uni-th>
<uni-th align="center">搜索uni-th>
uni-tr>
<uni-tr v-for="(item, index) in data" :key="index">
<uni-td align="center">{{ item.project }}uni-td>
<uni-td align="center">{{ item.url }}uni-td>
<uni-td align="center">{{ item.errmsg }}uni-td>
<uni-td align="center">{{ item.errtype }}uni-td>
<uni-td align="center">{{ item.reason }}uni-td>
<uni-td align="center">{{ item.solution }}uni-td>
<uni-td align="center">
<uni-dateformat :date="item.occurrence_timestamp" :threshold="[0, 0]" />
uni-td>
<uni-td align="center">
<uni-dateformat :date="item.handle_timestamp" :threshold="[0, 0]" />
uni-td>
<uni-td align="center">
<text v-if="item.state">已修复text>
<text v-else>待修复text>
uni-td>
<uni-td align="center">
<view class="uni-group">
<button
@click="navigateTo('../edit?id=' + item._id+'&dbname=vuelog_db', false)"
class="uni-button"
size="mini"
type="primary"
>
处理
button>
<button @click="confirmDelete(item)" class="uni-button" size="mini" type="warn">
删除
button>
view>
uni-td>
<uni-td>
<a
v-for="engine in engines"
:key="engine.url"
:href="engine.url.replace('ERR_MSG', encodeURIComponent(item.errmsg))"
target="_blank"
class="err-search"
>
{{ engine.name }}
a>
uni-td>
uni-tr>
uni-table>
<view class="uni-pagination-box">
<uni-pagination
show-icon
:page-size="pagination.size"
v-model="pagination.current"
:total="pagination.count"
@change="onPageChanged"
/>
view>
unicloud-db>
view>
页面写好之后别忘了在uniCloud admin 自带的菜单管理中注册路由信息,如果没有注册路由信息则页面无法在左侧菜单栏中显示。
为了让界面更美观,结合uni-app插件市场的ReportPro 数据报表(云函数版) 和秋云 ucharts echarts 高性能跨全端图表组件 升级一下页面的首页,做一个数据看板。这里放一下效果图。实现逻辑参考云函数操作云数据库:https://uniapp.dcloud.net.cn/uniCloud/cf-database
之所以要动手做这样一个项目,一方面是处于对技术的研究和探索,早在19年的时候就接触过微信小程序的云开发模式,不过一直都是做一些技术层面的探索和了解并没有真正动手实践;另一方面是随着自己开发的一些项目落地难免会有错误和bug发生,以往发生错误都是靠用户反馈然后滞后处理,导致用户体验非常糟糕,久而久之容易使用户对产品失去信心甚至产生质疑。由此便谋生了此项目的想法,也正好自己熟悉的uni-app 也推出了云开发模式,因此便有了本项目。
再说说对severless的感受,serverless即无服务器,这里的无服务器是对开发而言,服务器直接由云服务器供应商提供并代为管理了。如此一来开发人员就只需要关注业务,同时前后端的差异也越来越小,以本项目为例,全程没有后端参与也没有写过一条SQL语句,这一切得益于云服务商提供的一系列开发的API接口,当然方便的同时也带来一定的局限。使用 Serverless,我们不需要再过多关注服务端的运维,不需要关心我们不熟悉的领域,我们只需要专注于业务的开发、专注于产品的实现。我们需要关心的事情变少了,但我们能做的事情更多了。Serverless模式将进一步拓展前端的边界,现在的前端开发早已不是以往的前端开发,前端不仅可以做网页,还可以做小程序,做 APP,做桌面程序,现在前端也可以做服务端了!
Atwood’s Law: Any application that can be written in JavaScript, will eventually be written in >JavaScript.
任何可以用 JavaScript 来写的应用,最终都将用 JavaScript 来写。
一个完整的日志监控系统还应该包括消息通知模块,在我的初期架构中也是这样设想和规划的,由于消息通知借助的是第三方服务实现,是一个相对独立的功能模块,因此我将它独立出来,后续将整理为一篇单独的文章,给大家介绍一下uniCloud云函数如何调用第三方API,如何使用npm安装第三方服务。
最后,本文同步发布于个人G众号"前端知识营地",点击关注,获取更多优质有趣的内容。后续会将项目源码整理后放在公众号供大家参考,感兴趣的朋友可以点击下方链接点个关注!
前端知识营地点此关注
公众号原文链接
(完)