为了参加学校的一个网页设计比赛,小小地做了一个前后端分离的网页,可惜没有拿奖(还是太菜了😂)。写一下遇到的问题吧。
项目地址
前端:https://gitee.com/LuHawXem/pure-lake
后端:https://gitee.com/LuHawXem/pure-lake-server
涉及的编程语言、框架及工具
前端:Vue全家桶(Vue、Vuex、Router、Axios)
后端:Golang的Web框架Gin,使用Gorm进行数据库(Mysql)操作,使用Viper从yaml文件中加载配置
部署:Nginx作为Webserver接收请求
过程中遇到的问题
1.跨域
跨域,本质上是浏览器的同源安全策略限制,即浏览器只允许请求当前域(domain)的资源,是浏览器对JavaScript实施的安全限制。不允许跨域本质上是浏览器的限制(如果写一个没有同源策略限制的浏览器就不用担心跨域问题了)。
在MDN文档中对浏览器同源策略的解释为:
同源策略是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。
同源的定义为:protocol(协议)、host(主机)、port(端口)均相同,同源策略不关心页面路径即path部分的内容,同时,源可以被继承,在页面中通过about:blank
或javascript:
URL执行的脚本会继承打开该URL的文档的源,因为这些类型的URLs没有包含源服务器的相关信息。
在满足某些限制条件的情况下,页面可以修改它的源,脚本可以将document.domain
的值设置为其当前域或当前域的父域。如果将其设置为其当前域的父域,则这个父域会用于后续的源检查。同时,端口号是浏览器进行另行检查的,任何对document.domain
的赋值操作包括document.domain = document.domain
都会导致端口号被重写为null
,因此如果想让两个同协议同主机但不同端口的URL通过同源检测,只需要对document.domain
进行显式的赋值即可。
解决方案:
开发环境:
axios限制了开发环境的跨域资源请求,可以通过设置本地代理服务器devServer
来解决,在以@vue/cli
生成的vue项目中,我们需要在项目根路径下添加vue.config.js
文件,并在其中写入以下内容:
1 | const path = require('path') |
这里即是在本地localhost打开了一个代理服务器,通过代理服务器进行请求的转发,实现了同源,'api'
为路径重写,在上面的例子中,通过axios请求/api
即为请求https://localhost:4000/
,请求/api/path
即为请求https://localhost:4000/path
。
而在以vue-cli
生成的项目中,则需要在项目根目录下创建config
文件夹,并在其中创建index.js
文件,填入内容同上,只是需要把devServer
改成dev
。同时,如果是多个代理的话可以使用proxyTable
代替proxy
。
生产环境:
生产环境需要后端配置CORS以允许跨源访问,Gin下可以使用全局中间件,内容如下:
1 | //Cors处理函数 |
最重要的一步即为设置Access-Control-Allow-Origin
,可以设置为*
通配表示接受任意域名的请求,但此时浏览器出于安全考虑将不会在请求中发送Cookie
,或者直接设置为请求域。
同时,还需要设置允许的请求方法Access-Control-Allow-Methods
,注意,如果设置了允许POST
方法,必须同时设置允许OPTIONS
方法,因为在跨域情况下的POST
请求在发送前,会先发送一个OPTIONS
请求进行预检,同时需要对OPTIONS
方法进行特殊处理,返回状态码204
表示预检通过可以继续发送数据。
2. Axios
POST: axios发送post请求默认是使用application/json
格式发送的,而后端常用的Content-Type
一般是application/x-www-form-urlencoded
,如果直接使用axios的post请求会导致后端拿不到post参数,个人使用的解决方案是使用qs对param进行序列化,即data: qs.stringify(data)
,如果整个项目都是使用application/x-www-form-urlencoded
方式发送请求的话,可以创建一个axios的实例,并在实例上使用请求拦截器interceptors
对所有的请求的data进行qs序列化,代码如下:
1 | let config = { |
Formdata: axios发送formdata的请求方法貌似也是有点问题的,个人尝试过使用new Formdata()
的方式,但是最后失败了同样是无法传送数据,可能是qs序列化的问题?这个问题下一个项目再思考一下,后台处理Formdata的接口是没有问题的,用postman可以正常,感觉应该问题就是出在全局的qs序列化上了,后续项目再试试看。
3. Typescript
在这个项目中个人还尝试着使用了一下Typescript,算是见识到了什么叫严格的类型要求,一般来说个人使用网络请求的时候是这样的:axios(options).then(res => {}).catch(err => {})
,然后运行起来之后res和err这里都产生了报错(但不影响运行),查阅资料后发现,typescript不允许隐式的any类型变量,如果需要使用any类型变量需要显式声明,然后就换回来javascript了,这部分后续需要增强学习。
以前遇到的一些问题/知识点
1. go引入包
在go语言中,很多功能是通过一些扩展包实现的,与python类似,go的导包操作存在以下几种:
1 | import ( |
首先介绍第二种(导包顺序有讲究,被依赖的包需要先导入),第二种就是最常规的包导入,被导入后的包在当前项目文件中会使用它原有的名称,此处即为gorm
,而第三种导入则是显式地声明了导入后的名称,或者说是将后面的包导入了Conf
这个对象,后续使用这个包时就需要使用这个自定义对象(名称)。而第一种是比较特殊的导入,它使用了go语言中特有的 只写变量 _
。由于Go语言规定了,所有变量都必须被使用,但有时候从函数中获得的返回值可能并不被需要,所以就有了只写变量 _
用于抛弃值。所有赋给_
的值均会被抛弃。此处在导入包的操作中使用只写变量,用处是只引入包但不引入其中的函数,相当于对这个包进行了初始化,后续并不需要使用内部的函数,常用于环境等的初始化,比如路由注册等。
2. go语法糖
go中:=
运算符用于声明并用右值初始化变量,但可能出现作用域覆盖问题,如下:
1 | var Db *gorm.Db //声明全局公有变量Db |
此处会出现的问题就是,由于err变量是未声明的,我们可能会使用:=
声明并初始化err变量,并将函数返回值赋给Db和err,但是正是由于:=
运算符,函数内部会重新声明并初始化一个局部变量Db
,导致全局变量实际上并没有被初始化,后续使用全局变量时就会出现未初始化的报错,解决方案是先声明err变量再直接使用=
赋值,或者直接使用只写变量将err结果丢弃(前提是你能确定本次操作一定不会出错,不会有error情况出现),即解决方案如下:
1 | var Db *gorm.Db |
3. 作用域
1 | func Check(content string) (err error) { |
由于err变量已经在返回值列表中定义了,外层的return可以直接将这个err
变量作为返回值返回,因此不需要显式的return err
,但是在for循环内部,可以看到我们使用了:=
重新定义了一个局部变量err
,此时如果直接return的话会报错err is shadowed
,原因是err
变量被重复定义了,返回的是外部的err
变量,解决方案是要么使用新的变量名,要么显式地进行return err
。
4. Gorm创建数据库
创建数据库时一般需要指定charset即编码格式,如果需要支持emoji表情的存储,需要将charset指定为utf8mb4,如果只是指定为utf8的话,真实编码格式为utf8mb3,不支持emoji表情的存储。同时,如果需要支持时间格式的字段,需要手动指定parseTime=true