赶鸭子上架的全栈开发踩坑思考
完全赶鸭子上架的全栈开发之旅,过程可以说是很崩溃,做完回顾发现留坑过多,痛定思痛还是要重新整理一下代码结构,复盘整个项目搞清楚到底菜在哪里,下面是此次开发过程中的一些具体问题的总结。
项目概述
项目名称:数据分析平台
项目描述:对活动会场数据进行分析,提供模块化的结论
前端
框架选用:vue+vuex+vue-router
UI框架:element-ui
1. 关于在前台页面处理数据
初步考虑:一开始采用的方案是前台处理excel数据然后post数据到后台存储,主要考量有二,一是去除上传文件这一步骤,简化实现;二是前台对数据表格式进行校验,如果有错误可直接返回,减少不必要的带宽消耗。通过webworker也可以避免浏览器假死。
实际问题:低估了数据量级,实际拿到的excel表甚至超过了10MB,部分sheet甚至达到了百万条数据,浏览器的处理能力和内存有限,出现了out of memory
错误。
解决方案:将这一步骤移交给后台处理
2. 关于vue-router的使用
初步考虑:PC站点不可避免的有站内导航和跳转,另一方面为了提升体验,页面缓存也是需要的,综上考虑引入了vue-router。
实际问题01:贪图方便全员
keepAlive
了,导致页面缓存多了卡顿,懒惰是原罪
解决方案:去除不必要的keepAlive1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23// router配置
{
path: '/upload',
name: 'upload',
component: DataUpload,
meta: {
keepAlive: true // 需要被缓存
}
},
{
path: '/export',
name: 'export',
component: ReportExport,
meta: {
keepAlive: false // 不需要被缓存
}
}
// 引入页面
<keep-alive>
<router-view v-if="$route.meta.keepAlive" />
</keep-alive>
<router-view v-if="!$route.meta.keepAlive"/>实际问题02:需要新开页面展示某些信息
解决方案:由于信息量级小,所以最终通过url带参的方式实现,router页面跳转1
2
3
4
5
6
7
8
9
10
11
12// 新开页面
router.resolve({
name: "show-report-only",
query: {
url: url,
name: name
}
});
window.open(href, '_blank');
// 另一种带参跳转(不能新开页面)
router.push({ name: 'user', params: { userId: '123' }}) // 参数存储在内存中,不在url显示
3. 关于vuex的使用
初步考虑:前期考虑不足,典型的我坑我自己。现在就是后悔,非常后悔。
实际问题:vuex是在项目进行到一定程度之后才加进去的,整个项目组件之间存在很多的共享信息,包括基础分析数据/页面状态等,当代码量级上去之后,简单的组件之间传值已经无法维持数据共享,而且组件之间的依赖过强,代码可读性严重下降。
解决方案:加入vuex来留存全局的状态信息。
4. 多复用操作的实现:Vue.directive指令
实际问题:在实际的实现场景下,有一些功能是基本所有组件都会使用到的,比如说都存在一个按钮,点击之后需要进行一系列类似的操作,这个时候就需要自定义指令上场,避免成为ctrlC/ctrlV工程师。
解决方案:加入自定义指令,统一管理类似的功能,提升可复用性,降低后期修改成本。
nodejs后端
框架选用:express
代理服务器:nginx0. 关于框架选择的思考
在做这个项目之前没有正儿八经的用nodejs做后台开发,倒是之前在校期间,用过java和PHP,大概的思路是有的。基于这个前提下,还是保守的选用全家桶型框架express,使用之后感觉确实挺全的,但是有许多功能在实际使用中并不需要,后续考虑迁移到koa瘦个身。
1. post大数据超时
实际问题:后端最“重”的一个功能就是存数据,正如前端存excel表数据遇到的问题,将excel表上传到后端处理也有类似的问题,数据量过大,处理完成之后还要将几百万条数据存进数据库,容易造成超时,502/504错误。
解决方案:首选优化sql语句,加速处理;目前的快速解决方法是将express的socket连接超时时间。1
2
3server.on('connection', function (socket) {
socket.setTimeout(30 * 1000); // 超时时间,此处为30s
})
后续优化:将接口拆分为2个接口异步操作,一个用于执行处理数据,另一个接口用于获取处理进度。
2. 涉及文件云存储
前期疏漏:前期很理所当然的将文件存储在了服务器本地,实际上对于需要长期存储的数据应当有专门的文件服务器来存储,后端逻辑与存储空间应当隔离。
解决方案:使用公司内部的oss存储,该接口必须线上调用,本地调用可能失败或响应时间过长。
3. CORS问题
实际问题:对于附带身份凭证的请求,服务器端设置Access-Control-Allow-Origin
为*
无效
解决方案:res.header('Access-Control-Allow-Origin', req.header('origin'));
4. 关于Docker
作为一个菜鸟完全不知道docker是什么东西怎么用,此次部署是傻瓜式操作,完全是大佬写好的配置文件,直接走平台部署。不懂就要学,先简单的入个门,后续输出一些学习内容出来。
目前只是了解了一下Docker
是什么,拿来做什么。Docker
类似于虚拟机的概念,只是Docker
虚拟的是操作系统,它将同一个操作系统上管理的应用隔离开来,可以快速的创建和停止应用,不同应用之间互不影响。
数据库
mysql
1. 纯SQL语句与ORM库
实际问题:我肯定是脑子瓦特了,才会手写SQL,是框架不够好用还是加班不够久。
后续改进:为了后续项目能够更好兼容推进,改用ORM库来重构代码。
2. 数据库结构设计领悟
数据库真的是一门走一步要看三步的活计,前期设计作死在后面都是要还的。这一次设计过程中,又由于需求频繁变动,导致数据库修改避无可避,产生了一些非专业的感悟:
- 慎用自增ID,尤其是对于需要更新的数据,对于一些无法用单一字段作为
primary key
的表,可以考虑多个字段通过md5之类的加密算法,生成唯一的ID作为primary key
。 - 在前期需求不明朗,或者说项目最开始设计的时候,字段最好不要做过多的限制,比如说枚举类型,可以用tinyint来替代,后续如果有增减也不需要修改。
- 前期慎重使用
unique
或foreign key
之类的限制性功能,后期需求谁也说不准,很容易翻车。另外如外键之类的功能也降低了数据添加的可控性。 - 避免或尽量减少对一些较为特殊的内置字段类型/内置方法的使用,比如mysql的enum类型可以用varchar类型代替,timestamp类型可以用bigint存储时间戳代替,主要是考虑后续可能需要对多种数据库进行兼容。