重拾前端+后段重构
本文包含并不限于以下内容:
-
拿到 ECS 搞到域名后进行域名备案
-
Vite2 + Vue3 + TypeScript + Element-Plus 搭建前端项目
-
nodejs + express 搭建后端后使用 Go 重构后端
前言
之前本来想写个4月总结的,但4月确实没啥写的,加上4月还算在认真学习,就咕了,4月底决定和室友合租一服务器,之后花了大半个月的时间搞域名备案,期间呢,兴趣上来了,开始尝试用新生态借着服务器好好做一个前端项目,又拿 node 做了后端,5月过半,高数的复习陷入了一种莫名的疲软,感觉一直找不到做题的感觉,学到重积分,但题一直没法向前做着走,就准备搞点别的东西让自己缓缓(然而要准备开始复习专业课了,难受)。Golang,或者叫 Go 吧,Google 里的人开发的,静态强类型,编译型,很像 C,但比 C 安全、好用,天生支持高并发,用过的都说香(所以为什么函数不支持可选参数和默认参数)。所以拿来做后端 Web 服务针不戳,试试。
域名备案
这可真是个辛苦的差事,辛苦我室友了。
我们在去听某人工智能讲座的时候就开始讨论取域名的事,这也不是件容易的事 :joy:,换来换去才把价格高、被占用、不「好看」的域名给避开。然后就是让人头大的域名备案了,不是亲身经历还真不敢信这东西这么复杂。因为是从阿里买的域名,通过阿里备案,先在阿里过个审,填各种表,把各种各样的个人信息填了个全,就生怕网警找不到你。他们处理了好几天(还算快吧),然后有专人给我们打电话,说他们那边完事了,已经把信息给管局(通信管理局)了,还得去管局短信验证,然后,好家伙,「12天左右回复」,正好又赶上五一假,拖了有半个月管局才给回复说过了,叫把备案号挂网站上,又说可能按地方网警要求还得去搞个公安备案,又是「把身上有几颗痔数清楚了填上去」(你别说,就我写此文的时候,还没拿到公安备案号)。总结来说,要想备案,做好被「扒光」的准备以及十足的耐心。
搭建前端项目
Vue3 发布有一段时间了(春节左右发布),开始想接触接触了,加上 Vite2 发布了,看了一眼发现好像挺香的,这不又有服务器了嘛,怎么能没有想在浏览器地址栏敲上个 IP 就能看到点东西的冲动呢,所以就开始执行了。
虽然不知道要想做成个啥样子,但出于学习的目的就都试试(淦,我软件工程学了个啥?),第一步,新建 .psd,还是线在 notion 上开了个页面放自己的 Todo List,自己觉得这个还是很有必要的,知道自己一向做这些东西的时间跨度比较大,怕忘了自己做过什么骚操作结果忘了出大事。
然后就是喜闻乐见的新建文件夹开始疯狂 npm init @vitejs/app 选择 vue,选择 ts,写不来样式得找个组件库,嗯 npm install element -S ,哦淦,得是支持 Vue3 的版本,uninstall 了重新 npm install element-plus -S,那肯定得整个 Router,npm install vue-router@4,嗯,然后呢。。。
玩过一阵子的 Vue2,就照着之前的记忆开始建 components,开始建 router,虽然是 TypeScript,但好歹学过一点,做的时候还专门借了基本 ts 的教材,所以基础的写写没有太大问题,然后就是疯狂查 Element-Plus 的文档和 API 了,虽然方便,但用着难受,有着各种各样的 bug,就用了几个组件就修了几天 bug,这些 bug 来自 ts、来自组件本身、最多应该还是来自 Vue3。
Vue3 相较 Vue2 还是改了很多东西的(至少我用着把自己用自闭了),一个生命周期上的 setup() (也就是所谓的 Composition API ?),我找了各种视频讲解,对着文档仔仔细细看了好几遍才懂了个大概(视频都讲的比较底层,然而我现在是想知道怎么用,看得有点晕)。
这里列出一个绕了几天的问题做个例子。我准备在 HomePage 上放一个走马灯轮播室友给的猫猫图,所以找到 Element-Plus 的走马灯组件,最终选取了卡片式的这种类型
走马灯上的图片是通过 v-for 遍历上去的,我把 item 做成 img 标签,通过 :src=“item” 属性来填充图片地址,然后被遍历的对象应该是一个数组,里面放图片的静态位置或者通过图床后的 url,和室友讨论后决定使用图床。然后我想了想,准备把 url 保存在服务器,然后通过 GET 把它发回到前端来(虽然现在想想自己的操作挺迷幻了,不知道自己绕来绕去是追求个啥)。
后端接口很快写好,前段这边 Ajax 一下。然后呢,第一个问题来了,以前 Vue2 时的数据是放在 data() 里面的,Vite 给我的模版里面也确实有,那么我要做的就是在 Vue 组件挂载之前把 url 从后端那里拿到然后放到 data() 里就好了。
为了满足这个时机要求应该使用生命周期,放在 Vue2 我一般会放在 mounted 或者之前,但在这里使用的时候我去查了 Vue3 的 API,发现这里发生了改变
而且这些函数还得放 setup() 里面
这个还好说,用就是了,但是呢,我熟练的开始 this 然后惊人地发现得把 this 一层层往 setup() 里面引才能拿到最外面的 data 里面的数据容器,然后抱着疑惑的态度做了后震惊地发现 this 居然没有被定义(老人地铁手机.jpg),赶紧看了看文档
啊这。。。那咋办呢,又查文档发现 setup() 可以传俩参数,setup(props, context)
1 |
|
我发现这里的 props 应该是和 Vue2 的 data 一样是可以被插值语句或者 v- 的语句拿到的,拿我是不是可以把 url 注到 props 里面呢,做了发现,不能 :cry:,props 里的数据只读。
实在不行了,B 站里去找视频看,发现 Vue3 已经不把 data、methods 分开写了,setup() 里定义的变量也能直接被外界拿到 :cry:,那我直接放变量里不就行了。。。
这个倒是解决了,但是呢,又有毛病了,我发现 ts 不能用 contact 连接数组,咋办呢,拿我用对象储存吧,对象也能 v-for。这下总算把数据渲染上去了。
然而,问题又双叒叕来了,网页加载后,走马灯会中间会保持空白卡一会儿,然后才能正常运作,这我尝试了很多方法,没能解决,怀疑是 element-plus 的 bug。然后一气之下把 url 写成静态的,毫无毛病。。。
但是呢,还残存了一些不足,加载图片速度不行,怀疑是图床不行,现在想想,为什么不直接把图片写成静态呢。
搭建 Web 后端项目
Nodejs + express 搭建 Web 后端
Web 后端这边呢,还是用我熟悉的 nodejs 加上 express 框架,比起前端部分难为自己用 ts,果然还是 js 更加无拘无束。
快速地 npm install express,因为需要解析 html 文件 send 到前端,所以需要 npm install art-template express-art-template,因为要解析前端请求体,所以要 npm install body-parser,后端使用的是 MongoDB(js 不香吗),为了更好的使用,我们采用 Mongoose 框架,npm install mongoose
添加好请求头,做好跨域访问问题
1 |
|
把监听放在 80 端口,基本的初始化就完成了
1 |
|
然后连接数据库,这里之前踩过坑,在 mongoose.connect() 时要添加第二个参数 { useNewUrlParser: true, useUnifiedTopology: true },通过将 useNewUrlParser 设置为 true 来避免 “不建议使用当前 URL 字符串解析器” 警告
说道数据库就需要写一套 CRUD 来方便使用,目前只用到了 Create 和 Retrieve
先说 Create,mongoose 操作的方法是先定义一个 Schema,形式类似 C 的结构体,这里的作用就像关系型数据库里的表(也是说 MongoDB 是最像关系型数据库的非关系型数据库的原因吧),这里定义的时候需要把 Schema 指向我们需要的 collection。然后使用 mongoose.model 在挂上这些 Schema,最后再对这些 model 进行 save 和 find 操作。
值得注意的是,我们要将 储存、查询操作写成异步的方式,所以应该在查询函数中返回 Promise 函数,外部进行 .then 操作
以上就完成了初始化和数据库处理,之后的任务就是根据需要开放各种各样的端口,也就是写 router,这里理论上应该把 router 单独放一文件中,然后引入,做的更模块化,这边项目比较小,所以先放一起。
如果这些都做完,那一个简单的 Web 后端就搭建完成了(其实是在我所学范围内的东西全都用完了,叫啥来着,黔驴技穷?)。
用 Go 重构后端
为啥要搞这一档子事呢,前言也说了,目前呢,也是抱着多学点东西的目的在搞
学点 Go
从学校图书馆里借了四本书(其实只在看两本),学了大概一周,了解了个大概。这里只谈谈我觉得 Go 比较特殊的一些地方(相较我已经学过的一些编程语言,想到啥说啥)
作为静态强类型的语言,Go 包含了丰富的数据类型,我们从 Go 的反射那里了解到有如下:
1 |
|
我觉得 Go 比较好的一点就是可以在定义整型和浮点型的时候能够直接把位数写在类型上,如果不想写,Go 会智能地选择。其他的类型其实和 C 非常类似,比如结构体。
当我们定义一个变量时可以使用 var,然后在变量名后加上数据类型,但是 Go 是比较智能的,你可以直接使用 := 让 Go 自己去判断拿到的数据类型(当然,是要赋初值的情况)
注:其实 := 在别的地方我们也看到过,在 Python 中(3.8特性),它被称为「海象运算符」(确实挺可爱的),英文原文是 Assignment Expressions,使用它可以让 if 少写一行(当然,这只是个比较简单的例子,其实用处蛮多的):
1 |
|
而在 Go 中,它被称为短变量声明符,这其实和 Python 的「海象运算符」并不是同一个玩意儿,毕竟 Python 作为面向对象的脚本语言,并不是强类型的(虽然确实可以添加强制类型的特性,就像 ts 对 js 那样的做法),你不可能让 Python 拿这玩意儿来进行数据类型判断(本身 = 就已经有这效果了)。
Go 也有指针,和 C 很像,但个人认为比 C 更清晰,更不容易出问题。其实我在学 C 的时候,指针这块一直很晕,一直对指针这个东西的存在作用持有疑惑,也是在学 Go 的时候感觉对指针有了进一步的理解。
Go 的代码风格感觉不太像 C 也感觉不太像 JavaScript 或 Python,它不会像 py 那样规定缩进,也不需要在语句结束的位置给上一个 ; ,条件语句不需要也不能用小括号把条件括起来,但它要求条件语句、循环这种需要大括号的语句需要把 { 和关键词放在同一行
1 |
|
Go 还有一个很有特色的地方在循环语句,基本的循环和其他语言类似(只是不加小括号),主要是遍历,Go 采用的是 for-range 的形式:
1 |
|
你可以直接拿到被便利对象元素的序号和该元素本身,在 js 中会用 for…in 和 for…of 区分(前者拿到的是序号,后者拿到的是元素本身),这点很人性化。
Go 的比较像 Kotlin 或者说 TypeScript,要求参数类型,并且要求返回值类型,不同点在于,Kotlin 和 TypeScript 有泛型的概念,使得函数内的某些数据的类型保持一致,而 Go 中没有泛型,但是 Go 能够使用指针(指针函数、函数指针、指向函数的函数指针 .etc )
Go 的函数不具有默认参数和可选参数,要求我们传入的数据都是自定的切确定的,虽然少了灵活,但从一方面来讲,它也更安全(当然,C 也没有)
关于 Go 的函数,还有一点是比较特别的,它通过函数名的首字母是否大写来判断外文件是否能访问,当使用大驼峰命名(首字母大写),外部文件可以访问,而小驼峰(首字母小写)则作为一个内部的方法
最后,也是最神奇的一点,Go 的函数可以有多个返回值,最简单的,当我们在读取用户输入时
1 |
|
最后的 ReadString() 除了会返回一个 string 类型的数据外,还会返回一个 error
1 |
|
一般而言,我们会用一个变量 err 来装这个 error,但是必须对其处理,因为 Go 不能容忍有变量定义了却没有去用,当然你也可以使用下划线 _ 来忽略某返回值,但是多数情况下不被推荐
以上就是目前在学习中了解到的一些比较有意思的东西,下面我们继续讲项目
重构
第一次尝试用另一门语言去重构一个项目,因为项目比较简单,所以重构起来还算是比较容易的,更多的时间主要还是花在 debug 上
重构的步骤也和之前搭项目的步骤差不多,这里我使用了 gin 框架来搭建项目,使用 mgo.v2 来进行 MongoDB 操作,总体体验还行
这里就讲讲重构时的一些自我感觉比较好玩的地方或者 bug
宏观上来看我写的项目,全都放在一个文件里编译,特别是 router,堆在一起,不够模块化。另外就是任然存在着代码的冗余——我只将数据库操作中的写入单独做了个方法,但并没有写查询,导致这块大量重复。另外就是 error 的处理,手段倒是挺多,可以 fmt.Println(),可以 log.Fatal(),在看一些教程的时候也有写到使用 panic()(让 go 感到「惊慌」),这个可能需要在学习一段时间,看点别人写的项目才能用得顺溜
来聊聊 gin 和 mgo.v2 这俩东西吧
Gin is a web framework written in Go (Golang). It features a martini-like API with much better performance, up to 40 times faster thanks to httprouter. —— From https://gin-gonic.com/zh-cn/docs/introduction/
gin 作为一个 Web 后端框架,其实就相当于 express,不过更像 express 的其实是上面这段引用提到的 martini:
1 |
|
拿我为什么会选择 gin 呢,实际上我也是刚刚看了下 martini 才发现他更亲切 :joy:,不过体验上还是差不多的,主要就是在写路由上。
gin 在获取前端发过来的数据和从后端往前端发数据的时候,对参数的格式要求很严格,表单用一种方法承接,json 用一种方法承接。说道这个 json,你还得写个 struct,然后通过 BindJSON() 方法把 json 数据拿到,至少现在用着感觉还是有那么一点别扭。
mgo (pronounced as mango) is a MongoDB driver for the Go language that implements a rich and well tested selection of features under a very simple API following standard Go idioms. —— From http://labix.org/mgo
mongodb 的驱动在 Go 的 pkg 网站上有好多,看的教材也没有什么推荐,然后查了一下了解到这个驱动比较老牌,但是没有更新了。看了一下,还能用。主要就是 api 不好找,全英文读着还是很难受的(别骂了别骂了,在学了),不过逻辑还是比较简单的,多调试一下用起来不是什么大问题
基本完成了这个简单项目重构好了之后,直接 go build 一下,然后把编译好的 .exe 文件丢到服务器上就可以直接用了(服务器是 Windows 的,Linux 等学了之后再说),不用像 node 那样,还得装一个。
哦,别忘了一点,就是在打包使用时,在创建路由之前,将模式设置为 Release,这样可以关掉不必要的 debug 信息,关于日志管理,还有一个框架叫 logrus,我没用上,感觉比较复杂,就在这提一嘴,不过感觉这个东西还是有必要的,可能在后期会搞上这么一个。
小结
这次写的还挺长的,就是写的有点乱七八糟的,不过挺随心的,真的就想到啥写点啥,不过应该预期想写的都写上了,还算不错。项目还有点东西要做,下一阶段再做总结。
最后推荐一本 go 语言教材,《Head First Go 语言程序设计》—— [美] Jay McGavren。我很喜欢里面的箭头。