全栈工程师学习笔记
前端
本笔记中前端内容主要涉及Vue.js,以及部分前端通用知识。
1 脚手架的使用
1.1 命令行创建
使用vue-cli脚手架可以很方便地生成一个最简单的前端项目框架。下面是一个示例。
vue create frontend
其中的frontend
就是项目名称,这时候会在当前路径下生成一个frontend项目文件夹,我们之后就可以在这里面编写前端相关的内容。
1.2 图形界面创建
vue ui
上述命令会打开一个浏览器窗口,并以图形化界面将你引导至项目创建的流程。
1.3 项目结构
|-- build // 项目构建(webpack)相关代码
| |-- build.js // 生产环境构建代码
| |-- check-version.js // 检查node、npm等版本
| |-- utils.js // 构建工具相关
| |-- vue-loader.conf.js // webpack loader配置
| |-- webpack.base.conf.js // webpack基础配置
| |-- webpack.dev.conf.js // webpack开发环境配置,构建开发本地服务器
| |-- webpack.prod.conf.js // webpack生产环境配置
|-- config // 项目开发环境配置
| |-- dev.env.js // 开发环境变量
| |-- index.js // 项目一些配置变量
| |-- prod.env.js // 生产环境变量
| |-- test.env.js // 测试脚本的配置
|-- src // 源码目录
| |-- components // vue所有组件
| |-- router // vue的路由管理
| |-- App.vue // 页面入口文件
| |-- main.js // 程序入口文件,加载各种公共组件
|-- static // 静态文件,比如一些图片,json数据等
|-- test // 测试文件
| |-- e2e // e2e 测试
| |-- unit // 单元测试
|-- .babelrc // ES6语法编译配置
|-- .editorconfig // 定义代码格式
|-- .eslintignore // eslint检测代码忽略的文件(夹)
|-- .eslintrc.js // 定义eslint的plugins,extends,rules
|-- .gitignore // git上传需要忽略的文件格式
|-- .postcsssrc // postcss配置文件
|-- README.md // 项目说明,markdown文档
|-- index.html // 访问的页面
|-- package.json // 项目基本信息,包依赖信息等
- main.js 程序入口文件,加载各种公共组件
第一部分(import)导入要用到的组件
第二部分新建了一个Vue对象,组件里面写的是App,也就是说导入 App.vue,在入口的时候将app的东西都显示出来。 - App.vue
这里引入了一个组件componentsA,直接以标签的形式加在html里面。
要在下面的export default里面的components进行注册,不然是用不了的。
对于组件的理解:组件是可以复用的Vue实例,带有名字,也可以说是一个自定义元素。是页面的一部分,就是可能其他地方也要用到,所以就封装起来,所以要用的时候进行导入,方便复用。
2 基础语法知识
2.1 实例化
var app = new Vue({
el: '#app', //绑定到元素上
data: {
message:'hello world'
},
filters:{
filterName1:function(){},
filterName2:function(){}
},
methods:{
methodName1:function(){},
methodName2:function(){}
},
//生命周期钩子
created:function(){
//未挂载到dom之前触发
},
mounted:function(){
//挂载到dom后触发
},
beforeDestroy:function(){
}
})
2.2 过滤器
{{ data中的变量名 | filter中的过滤器名 }}
2.3 指令和事件
- v-text:解析文本,和{{ }}作用一样
- v-html:解析html
- v-bind:动态更新HTML元素上的属性
- v-on:绑定事件监听器
- v-bind语法糖:冒号 :
- v-on语法糖:@
- v-bind的作用,以及v-bind的变量语法,数组语法,对象语法:
- v-bind通常用来绑定属性的,格式是v-bind:属性名 = “值”,简写:属性名 = “值”
- 变量语法:v-bind:class = “变量”,变量形式 ,这里的变量的值,通常是在css定义好的类名;
- 数组语法:v-bind:class= “[变量1,变量2]” ,数组形式,其实跟上面差不多,只不过可以同时绑定多个class名;
- 对象语法:v-bind:class = {classname1:boolean,classname2:boolean},对象形式,这里的classname1(2)其实就是样式表中的类名,这里的boolean通常是一个变量,也可以是常量、计算属性等,这种方法也是绑定class最常用的方式。
2.4 内置指令
-
v-cloak:解决页面没加载完的时候出现类似
{{ msg }}
等变量名的现象,一般和display:none
一起使用<style> [v-cloak]:{ display-none; } </style> ------------------------- <p v-cloak> {{ msg }} </p>
-
v-once:只在页面中渲染一次,改变变量的值也不会重新渲染
<p v-once> {{ msg }} </p>
-
条件指令:v-if、v-else-if、v-else
- v-if:后面接的是等号,等号后面的内容必须是布尔值,布尔值为true则渲染,否则不渲染
<p v-if = '6>3'>{{ apple }}</p> <p v-else-if = '9<6'> {{ banana}} </p> <p v-else> {{ orange }} </p>
- v-if的弊端:Vue在渲染元素时,出于效率考虑,会尽可能的复用已有的元素而非重新渲染
- 解决方法:加key,唯一,提供key值可以来决定是否复用该元素
-
v-show:只改变display属性
-
v-if和v-show的区别:
v-if:实时渲染:页面显示就渲染,不显示就移除 v-show:元素始终存在于页面中,只是改变了display属性
-
-
v-for
- 遍历多个对象:data 里面是一个数组
<div id='app'> <ul> <!-- intm in items --> <li v-for="fruit in fruits">{{ fruit.name }}</li> </ul> <ul> <!-- 带索引的写法 --> <li v-for="(fruit,index) in fruits">第{{ index }}个 {{ fruit.name }}</li> </ul> </div> <script> var app = new Vue({ el: '#app', data: { //这里是一个数组 fruits: [ {name: 'apple'}, {name: 'banana'}, {name: 'orange'} ] } }) </script>
- 遍历一个对象的多个属性
<div id='app'> <span v-for='value in fruits'>{{ value }}</span> <!-- 带v-k-i的写法:value key index (外开) --> <p v-for="(value,key,index) in fruits2"> value:{{value}}<br> key:{{key}}<br> index:{{index}} </p> </div> <script> var app = new Vue({ el: '#app', data: { //这里是一个对象 fruits: { fruit1: 'apple', fruit2: 'banana', fruit3: 'orange' } } }) </script>
2.5 计算属性
-
基础例子
<div id="example"> <p>Original message: "{{ message }}"</p> <p>Computed reversed message: "{{ reversedMessage }}"</p> </div> var vm = new Vue({ el: '#example', data: { message: 'Hello' }, computed: { // 计算属性的 getter reversedMessage: function () { // `this` 指向 vm 实例 return this.message.split('').reverse().join('') } } })
-
计算属性的setter
计算属性默认只有getter,不过在需要时你也可以提供一个setter:
在赋值的时候会触发set函数,把新赋的值以参数的形式传递进去// ... computed: { fullName: { // getter get: function () { return this.firstName + ' ' + this.lastName }, // setter set: function (newValue) { var names = newValue.split(' ') this.firstName = names[0] this.lastName = names[names.length - 1] } } } // ...
现在再运行
vm.fullName = 'John Doe'
时,setter会被调用,vm.firstName
和vm.lastName
也会相应地被更新。
2.6 数组更新、过滤与排序
改变数组的一系列方法:
- push() 在末尾添加元素
- pop() 将数组的最后一个元素移除
- shift() 删除数组的第一个元素
- unshift():在数组的第一个元素位置添加一个元素
- splice() :可以添加或者删除函数—返回删除的元素
两种数组变动vue检测不到:改变数组的指定项;改变数组长度
对应解决方案:
Vue.set(app.arr,1,”car”);//用来改变指定项
app.arr.splice(1);//用来改变数组长度
3 组件
组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以表现为用 is 特性进行了扩展的原生 HTML 元素。所有的 Vue 组件同时也都是 Vue 的实例,所以可接受相同的选项对象 (除了一些根级特有的选项) 并提供相同的生命周期钩子。我们的项目中就大量运用到了组件这一功能,如视频播放功能就根据前端布局分成了5大部分。
3.1 组件用法
-
全局注册
Vue.component('component-name',{ template: '<div>组件内容</div>' }) //优点:所有Vue实例都可以使用 //缺点:权限太大,容错率低
-
局部注册
var app = new Vue({ el: '#app', data:{}, components: { 'component-name': { template: '<div>组件内容</div>' } } })
-
这里有几点需要注意:
-
组件名用横杆命名法,不能用驼峰命名法(这一点与Gin里面通过首字母大写、python里面通过下划线来区分函数可见性有点像,但也就是这种语法检查会让人觉得有点烦,我一开始在我们的小组项目中就用的是驼峰命名法,后来看到IDEA的智能提示才逐渐改了过来)
-
template中的内容必须被一个DOM元素包起来(如果真的有多个元素,可以考虑外边套一个div)
-
在组件的定义中,除了template,还可以有data、methods、computed选项
-
data必须是一个方法(它返回了真正的变量),以下两种写法都可以接受(我用的是第二种)
//第一种方法 data: function(){ return { message: 'hello world' } } //第二种方法 data() { return { message: 'hello world' } }
-
3.2 props父组件向子组件传递数据
- 在组件中使用props从父组件接收参数,props中定义的属性,在组件送可以直接使用
- props来自父级,而data中return的数据是自己组件本身的数据,两种数据的作用域都是组件本身,可以在template、methods、computed中直接使用
- props的值有两种,一种是字符串数组,一种是对象
- 可以使用vbind动态绑定父组件来的内容
<div id="app"> ---用v-bind传递数据(需要回车)-------------------------<br> <input type="text" v-model.lazy="parentMsg"> <bind-component v-bind:msg='parentMsg'></bind-component> </div> var app = new Vue({ el: '#app', data: { count: 0, parentMsg: '这是来自父级的数据' }, components: { 'bind-component': { props:['msg'], template: '<div>{{ msg }}</div>' } } })
3.3 单向数据流
- props传递数据是单向的,父级数据的变化会传递给子级,反之就行不通了
- 单向数据流可以避免子组件无意中修改父组件状态,这保证了父组件在优先级上高于子组件
- 应用情景:
- 第一种:父组件传递初始值进来,子组件将它作为初始值保存起来,在自己的作用域
下可以随意使用和修改。
<div id="app"> <my-component msg="来自父级的数据"></my-component> </div> ---js---------------------------------------------------- <script> var app = new Vue({ el: '#app', data: {}, components: { 'my-component': { props: ['msg'], template: '<p>{{ newMsg }}<p>', data: function () { return { //在data中保存下来 newMsg: this.msg } } } } }) </script>
- 第二种:需要被转变的原始值传入,通过props获取,通过计算属性进行转换。
<div id="app"> <input type="number" v-model="width"> <computed-component :wth='width'></computed-component> </div> <script src="https://cdn.jsdelivr.net/npm/vue"></script> <script> var app = new Vue({ el: '#app', data: {width:10}, components: { 'computed-component': { props: ['wth'], template:'<div :style="style"></div>', //用计算属性转换传入的值 computed: { style: function(){ return { width: this.wth + 'px', height: '100px', background: 'red' } } } } } }) </script>
- 第一种:父组件传递初始值进来,子组件将它作为初始值保存起来,在自己的作用域
3.4 数据类型验证
- 验证的 type 类型可以是:
- String
- Number
- Boolean
- Object
- Array
- Function
Vue.component(‘ my-compopent ’,{
props : {
//必须是数字类型
propA : Number ,
//必须是字符串或数字类型
propB : [String , Number] ,
//布尔值,如果没有定义,默认值就是 true
propC: {
type : Boolean ,
default : true
},
//数字,而且是必传
propD: {
type: Number ,
required : true
},
//如果是数组或对象,默认值必须是一个函数来返回
propE: {
type : Array,
default : function () {
return [] ;
}
},
//自定义一个验证函数
propF: {
validator : function (value) {
return value > 10;
}
}
}
});
4组件通信
4.1 自定义事件
自定义事件用于让子组件给父级传递数据。
子组件用$emi(‘父组件中事件名’, 要传递的数据)来触发事件,父组件用@‘父组件中事件名’='触发的函数’来 监听子组件的事件,触发的函数的参数即为子组件传递来的数据。
具体步骤如下:
第一步:自定义事件;
第二步:在子组件中用$emit触发事件,第一个参数是事件名,后边的参数是要传递的数据;
第三步:在自定义事件中用一个参数来接受。
<div id="app">
您的余额是:{{ acount }}<br>
<my-component :acount="acount" @change-acount='handleAcount'></my-component>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
acount: 2000
},
methods:{
handleAcount:function(value){
this.acount = value
}
},
components: {
'my-component': {
props:['acount'],
template: `<div>
<button @click="addAcount">+1000</button>
<button @click="reduceAcount">-1000</button>
</div>`,
data: function(){
return {
count: this.acount
}
},
methods:{
addAcount: function(){
this.count += 1000
//注意这里$emit的参数
this.$emit('change-acount',this.count)
},
reduceAcount: function(){
this.count -= 1000
this.$emit('change-acount',this.count)
}
}
}
}
})
4.2 在组件中使用v-model
在组件中使用vmodel其实是一个语法糖,这背后其实做了两个操作:利用v-bind绑定一个 value属性,然后利用v-on指令给当前元素绑定input事件。
$emit(‘input’,value)的代码,这行代码实际上会触发一个input事件, ‘input’后的参数就是传递给vmodel进行绑定。
<div id="app">
您的余额是:{{ acount }}<br>
<!-- <my-component :acount="acount" @change-acount='handleAcount'></my-component> -->
<!-- 下面使用v-model -->
<my-component :acount="acount" v-model='acount'></my-component>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
acount: 2000
},
components: {
'my-component': {
props:['acount'],
template: `<div>
<button @click="addAcount">+1000</button>
<button @click="reduceAcount">-1000</button>
</div>`,
data: function(){
return {
count: this.acount
}
},
methods:{
addAcount: function(){
this.count += 1000
// 这里触发的是input事件
this.$emit('input',this.count)
},
reduceAcount: function(){
this.count -= 1000
this.$emit('input',this.count)
}
}
}
}
})
</script>
4.3 非父组件之间的通信—bus中介
用于两个组件之间的通信,(非父子关系),可以在根组件中用一个空的Vue对象作为中介,一个组件中监听该中介的自定义事件,另一个组件中触发该自定义事件并传递数据。
<div id="app">
<acomponent></acomponent>
<bcomponent></bcomponent>
</div>
<script>
Vue.component('acomponent', {
template: '<button @click="handle">点击向B组件传递数据</button>',
data: function () {
return {
msg: '我是来自A组件的数据'
}
},
methods: {
handle: function () {
// 触发根组件的bus中的method1事件
this.$root.bus.$emit('method1', this.msg)
}
}
})
Vue.component('bcomponent', {
template: '<div>{{ msg }}</div>',
data: function () {
return {
msg: '这是B组件'
}
},
created: function () {
// 对根组件bus监听method1事件
this.$root.bus.$on('method1', (value)=> {
this.msg = value
})
}
})
var app = new Vue({
el: '#app',
data: {
bus: new Vue()
}
})
</script>
4.4 父链和子链
父链:this.$parent
子链:this.$refs.索引
<div id="app">
<button @click='clickA'>A子组件</button>
<button @click='clickB'>B子组件</button>
<son-a ref='a'></son-a>
<son-b ref='b'></son-b>
</div>
<script>
var app = new Vue({
el: '#app',
data: { msg: '来自父组件的msg' },
methods: {
clickA: function(){
alert(this.$refs.a.name)
},
clickB: function(){
alert(this.$refs.b.name)
}
},
components: {
'son-a': {
template: '<div>这是子组件:{{ msg }}</div>',
data: function () {
return {
msg: this.$parent.msg,
name: 'son-a'
}
}
},
'son-b': {
template: '<div><div>',
data: function () {
return {
name: 'son-b'
}
}
}
}
})
</script>
后端
1 Golang基本语法
1.1 变量声明
//通用形式,指定变量名,变量类型,变量值
var name int = 99
fmt.Println(name)
//指定变量名,以及变量类型,未指定值的时候默认是类型零值
var age int
//可以后面赋值
age = 88
fmt.Println(age)
//短变量声明,指定变量名,以及变量的值,而变量类型会自动由值的类型决定。
gender := "male" //gender变量的类型是string
fmt.Println(gender)
//变量列表 要求:左右两边变量名与值的数量相同
//先定义,然后赋值
var x, y, z int //x,y,z都是int类型
x, y, z = 1, 2, 4
fmt.Println(x, y, z)
//先定义,同时赋值
var o, p, q = "abc", true, 3.14
fmt.Println(o, p, q)
//使用短变量的变量列表形式,需要注意,左边至少有一个新变量声明,否则报错
o, r, s, t := "changed", "xyz", false, 99
fmt.Println(o, r, s, t)
注意:
在函数中(包括自己定义的函数或者主函数中)声明的变量如果没有使用,在编译的时候,会报错。
在函数外面定义全局变量不能使用短变量形式。
1.2 常量
const x = 13
const p int = 13
const pp, ppp = 1, 2
const (
i = 10
ii = 20
)
在声明常量的时候,必须指定值,否则出错。
声明在函数中的常量不使用,并不会报错,只有声明的变量或者导入的包未使用才会报错。
注意下面这个用法:
const (
i = 1
ii
iii = 2
iiii
)
fmt.Println(i, ii, iii, iiii)// 1 1 2 2
定义多个常量时,如果某个变量没有执行值,那么就会沿用上一个常量的值。
1.3 const与iota
在和const联用时,iota的作用是:iota初始值为0,每出现一个常量,iota就加1。
const (
i = iota
ii
iii = 4
iiii = iota
)
fmt.Println(i, ii, iii, iiii) //0 1 4 3
1.4 数据类型
基本数据类型:string、bool、intX、floatX
复合数据类型:array、struct
引用类型:point、slice、map、func、channel
接口类型:interface
1.5 运算符
和其他语言一样,注意–、 ++只能后置,即只能i++、i–。
逻辑运算符||、&&、!,这3个运算符左右两边的表达式必须是bool型,不能是int或者string类型。
1.6 类型强制转换
在两种类型的变量相互赋值或者进行比较时,Go语言不会像其他语言一样进行隐式转换,所以必须进行显式地转换。
所以下面两种做法是不可行的:
var f1 float32 = 3.14
var f2 float64 = 6.28
//类型不同,不能进行比较,会报错
// fmt.Println(f1 == f2)
//类型不同,不能进行运算
f3 := f1 - f2
fmt.Println(f3)
solution:
var f1 float32 = 3.14
var f2 float64 = 6.28
//进行强制转换
f := float32(f2)
fmt.Println(f1 == f) //false
f3 := f1 - f
fmt.Println(f3) //0
1.7 复数
c := complex(3, 4)
fmt.Println(real(c), imag(c))//3 4
1.8 布尔值
**注意:**bool值的true或者false,并不会自动转换为整型的1或者0。
1.9 字符
单引号只能用来表示单个字符,不能用来括字符串;双引号技能括单个字符,又能括字符串。
a := 'ab' //wrong
b := 'x' //correct
c := "y" //correct
d := "abc" //correct
1.10 字符串
str1 := "你 "
str2 := "好"
//使用 + 进行字符串拼接
str3 := str1 + str2 //你 好
fmt.Println(str3)
length := len(str1)
//使用len()求字符串的长度时,求的是字节数,不是字符数
fmt.Println(length) //4
str := "hello world"
//取某一个索引的字符
fmt.Println(str[0]) //104
fmt.Println(string(str[0])) //h
//错误用法,不能修改字符串内部的值
//str[0] = "d"
//可以改变字符串的值
str = "go to hell"
fmt.Println(str) //go to hell
1.10.1 字符串截断
截断格式str[start:end],左闭右开区间,即end位置的字符不会包含在内。省略start时,默认start为0。省略end时,默认end为字符串长度len(str)。
str := "abcdefg"
fmt.Println(str[2:4]) //cd
fmt.Println(str[2:]) //cdefg
fmt.Println(str[:4]) //abcd
1.10.2 字符串遍历
str := "abcdefg"
length := len(str)
for i := 0; i < length; i++ {
fmt.Println(str[i], string(str[i]))
}
for index, value := range str {
fmt.Println(index, string(value))
}
1.10.3 原生字符串字面值
使用``代替双引号。可以保存内容的格式,原样输出.
str := `
menu:
1、
2、
`
fmt.Println(str)
1.11 指针
var num int = 10
var p *int = &num
// 等价于
//p := &num
fmt.Println(num, p, *p) //10 0xc42001a0f0 10
注意:
指针的默认值是nil,不是NULL,并且go语言中没有NULL常量
使用&取地址,使用*解引用
支持C语言类似的指针运算,比如指针自增等。
使用“.”来访问目标成员,不支持“->”运算符
不存在函数指针
1.11.1 结构体指针
package main
import "fmt"
type person struct {
Name string
Age int
}
func main() {
p := &person{"abc", 90}
name := p.Name
//等价于
//name := (*p).Name
fmt.Println(name)
}
1.11.2 数组指针
arr := [3]int{99, 999, 9999}
p := &arr
fmt.Println(arr) //[99 999 9999]
fmt.Println(p) //&[99 999 9999]
fmt.Println(*p) //[99 999 9999]
1.11.3 指针数组
var p [3]*int
x, y, z := 1, 11, 111
p[0], p[1], p[2] = &x, &y, &z
fmt.Println(p[0], p[1], p[2]) //0xc42001a0f0 0xc42001a0f8 0xc42001a100
fmt.Println(*p[0], *p[1], *p[2]) //1 11 111
1.11.4 二级指针
var num int = 10
p := &num //p保存着num的地址
pp := &p //pp保存着p的地址,即地址的地址
fmt.Println(p, *pp) //0xc42001a0f0 0xc42001a0f0
fmt.Println(&p, pp) //0xc42000c028 0xc42000c028
fmt.Println(*p, **pp) //10 10
1.12 type定义类型
使用type关键字定义新的类型名,一般出现在包一级,即函数外部,如果新建的类型名字首字母大写,则在包外可以使用,否则只能在包内使用。
格式:type new_type base_type
注意type和C语言的typedef不同,typedef只是定义别名而已,而Go语言的type不仅定义类型名称,还包括类型的行为。
package main
import "fmt"
type Score int
func (s Score) Pass() bool {
return s >= 60
}
func main() {
var s1 Score = 69
fmt.Println(s1.Pass()) //true
}
虽然使用type关键字定义了一个新的类型,就像上面的Score和int,虽然在底层都是int,底层都是同一个类型,但是:不能直接进行比较、计算,必须要先转换后才可比较和计算。
1.13 标准输入输出
1.13.1 Print、Println、Printf
a, b, c := 1, "abc", true
fmt.Println(a, b, c) //1 abc true
fmt.Print(a, b, c, "\n") //1abctrue
fmt.Printf("%d,%s,%t\n", a, b, c) //1,abc,true
1.13.2 Scan
var (
name string
age int
)
for {
fmt.Println("please intput name,age")
fmt.Scan(&name, &age)
fmt.Println("name:", name, "\t age:", age, "\n")
}
运行结果如下:
please intput name,age
abc 30
name: abc age: 30
please intput name,age
abc
50
name: abc age: 50
please intput name,age
abc 50 xyz 99
name: abc age: 50
please intput name,age
name: xyz age: 99
1.13.3 Scanln
var (
name string
age int
score int
)
for {
fmt.Println("please intput name,age,score")
fmt.Scanln(&name, &age, &score)
fmt.Println("name:", name, "\t age:", age, "\t score:", score, "\n")
}
运行结果如下:
please intput name,age,score
abc
name: abc age: 0 score: 0
please intput name,age,score
abc 30
name: abc age: 30 score: 0
please intput name,age,score
abc 30 99
name: abc age: 30 score: 99
1.13.4 Scanf
var (
name string
age int
)
fmt.Println("please intput name,age")
fmt.Scanf("%s %d", &name, &age) //输入时以空格分隔
fmt.Println("name:", name, "\t age:", age, "\n")
fmt.Println("please intput name,age")
fmt.Scanf("%s,%d", &name, &age) //输入时,以逗号分隔
fmt.Println("name:", name, "\t age:", age, "\n")
运行结果:
please intput name,age
abc 99
name: abc age: 99
please intput name,age
xyz 88
name: xyz age: 99
注意,Scan并不会在遇到回车换行的时候结束输入。而Scanln和Scanf都是一旦遇到回车换行,就认为本轮输入(一个Scanln或者Scanf)结束,执行下一个Scanln或者Scanf。当然,缓冲的数据,可以自动赋值。
1.14 if选择结构
if score := 61; score > 80 {
fmt.Println("优秀")
} else if score > 60 {
fmt.Println("及格")
} else {
fmt.Println("不及格")
}
注意:
花括号不可省,切不可另起一行。
如果要声明变量,可以在if后面声明,并用分号分隔,分号后面跟条件。
最最重要的是:
if后面的条件必须是bool类型的值或者结果为bool类型的表达式,不可以是整型或者其他类型。
1.15 switch选择结构
var length int = 3
switch length {
case len("abc"): //可以是表达式
fmt.Println(3)
case 4:
fmt.Println(4)
default:
fmt.Println(5)
}
//输出
//3
Go语言中的switch case结构中,每一个case条件分支末尾不用加break;
case可以后面的条件可以使变量,也可以是常量,但是有一点:case后面的类型,要和switch后面的类型相同。
fallthrough关键字的作用如下:
var score int = 12
switch score {
case 6:
fallthrough
case 12:
fmt.Println(12)
fallthrough
case 24:
fmt.Println(24)
default:
fmt.Println(36)
}
上述代码会输出12和24。
如果某个case里面有fallthrough,那么,这个case的紧接着的下一个case条件会默认满足,直接执行写一个case中的语句,并不会判断下一个case的条件是否满足。
case可以这样写:
var score int = 12
switch score {
case 6, 12:
fmt.Println(6, "或者", 12)
default:
fmt.Println(36)
}
省略switch后面的表达式,此时相当于if else。
var score int = 90
switch {
case score > 80:
fmt.Println("优秀")
fallthrough
case score > 60:
fmt.Println("及格")
default:
fmt.Println("不及格")
}
上述代码会输出“优秀”和“及格”。
1.16 for循环
Go语言中没有while循环和do…while循环,只有一个for循环,但是for循环有三种形式,可以替代while循环和do while循环。
for i:=0;i<10;i++{
fmt.Println(i)
}
for {
fmt.Println("这是死循环,相当于while(1)")
}
for i,v := range m{
fmt.Println("同样是死循环")
}
上面三种用法分别对应其他语言中的for循环,while()循环,do…while循环。
在循环中可以使用break和continue来控制循环。
2 Gin框架
2.1 启用go mod
export GO111MODULE # 开启go mod支持
go mod init project_name # 初始化 go mod
go get -v github/go-gnic/gin@v1.4.0 # 安装gin包
2.2 main.go
r := gin.Default() // 获取默认服务器对象
r.GET("/path", func (ctx *gin.Context){ // 绑定路由地址 get请求 以及 /path 后面执行匿名回调函数
ctx.JSON(200, gin.H{ // http response 响应 200 以json格式输出数据
"state": 1
"data":"data",
"msg":"获取数据成功",
})
})
r.Run("127.0.0.1:8080") // 阻塞 监听127.0.0.18080端口
2.3 请求路由
1. http常见的请求GET、POST、DELETE、PATCH等路由实现
1.1. 常见绑定路由方法名称:
r.GET(path, func (ctx *gin.Context){
})
其他方法同理。
1.2. Handle方法
其实上面的方法底层还是调用了这个Handle方法
r.Handle(http_method, path, func(ctx *gin.Context){ // 绑定请求method方法 路径path 回调函数
})
2. 绑定静态static目录
r.Static("/css/", "./css") // 1.绑定css目录访问,本质上使用StaticFS做了一层封装而已
r.StaticFS("/js/", http.Dir("./js")) // 第二种方式。
r.StaticFile("/hello.php", "./hello.html")
3. 动态url参数(固定参数以及泛参数)
3.1. 通过url传递固定参数
r.GET("/get/:name/:id", func(c *gin.Context){ // 绑定固定参数 name id
name := c.Param("name") // 获取name参数
id := c.Param("id") // 获取id参数
c.JSON(200, gin.H{
"state":1,
"name": name,
"id": id
})
})
3.2. 泛url绑定
r.GET("/user/*action", func(c *gin.Context){ // 泛绑定前缀所有的/user/xx user/abc 等url都走到这里
})
2.4 获取http请求数据
1. 获取get参数
name := r.Query("name")// 不带默认值的获取参数
age := r.DefaultQuery("age")// 带默认值的获取参数
2. 获取post参数
name := c.PostForm("name")
age := c.DefaultPostForm("age")
3. 获取body参数
strByte ,err := ioutil.ReadAll(context.Request.Body) // 从http request body中读取数据
在读取body之后,通过PostForm()和DefaultPostForm()函数获取post参数会失效。
解决方案:把body读到的数据又重新放到Request.Body里面就可以了。
ioutil.NopCloser(bytes.NewReader(strByte)) // 重新写好回 Request.Body里面就可以了。
4. 绑定参数到struct结构体
// 定义结构体类
type person struct{
Name string `form:"name"` // 加上form的tag标签
Gender string `form:"gender"`
BirthDay time.Time `form:"birthday" time_format:"2006/01/02"`
}
var p person
err := context.ShouldBind(&p) // 自动注入person的实例中
5. 校验参数格式以及I18N
5.1. 结构体验证参数
常见校验tag:form、time_format、binding
// 定义结构体类
type person struct{
Name string `form:"name" binding:"required"` // 必须
Gender string `form:"gender"`
Age int `form:"age" binding:"gt=10"` // 必须传且 大于10
BirthDay time.Time `form:"birthday" time_format:"2006/01/02"`
}
校验规则详细文档: https://godoc.org/gopkg.in/go-playground/validator.v8
5.2. 自定义校验规则
基于v8校验去实现一个tag,实现一个tag也就是实现一个方法。
具体文档: https://godoc.org/gopkg.in/go-playground/validator.v8#hdr-Custom_Functions
6. 多语言I18N
使用v9校验器。
7. 上传文件uploadFile
file, err := ctx.FormFile("file") // 获取到上传的文件对象
// file.FileName 文件名称 file.Size 文件大小
ctx.SaveUploadFile(file, "/path/" + file.FileName) // 保存上传文件
2.5 中间件
1. 默认gin.Default()使用了logger和recover中间件
f,_ := os.Create("gin.log") // 可以将logger的内容重定向写入到日志log中
gin.DefaultWriter = io.MultiWriter(f)
gin.DefaultErrorWriter = io.MultiWriter(f)
r := gin.New() // 空白app对象
r.Use(gin.Logger()) // 使用中间件
2. 自定义中间件
自定义一个func,返回值是gin.HandleFunc函数即可。
func IpblockMiddleware() gin.HandlerFunc {
return func(ctx *gin.Context) {
ipLists := []string{
"127.0.0.2",
}
clientIp := ctx.ClientIP()
flag := false
for _,ip := range ipLists {
if ip == clientIp {
flag = true
break
}
}
if !flag {
ctx.String(401, "权限有误: %s 禁止访问", clientIp)
ctx.Abort() // 结束中间件往下执行
}
}
}
r.Use(IpblockMiddleware) // 这样使用中间件即可
2.6 关闭或者停止服务器server
1. 模板渲染
r.LoadHTMLGlob("templdate/*")
r.HTML(200,"index.html",gin.H{ //设置数据传递给index.html
"title":"标题"
})
在index.html中使用模板渲染:
<!doctype html>
<html>
<h1> {{.title}} </h1> // 渲染title (标题)
</html>
2. https整数自动配置
autotls.Run(r, "www.baidu.com")
2.7 企业级脚手架 gin框架
1. 自己可以把项目目录结构设计成类似这样:
-----------------conf
--------------config.yaml
-----------------controllers
-----------------routers
-----------------dao
-----------------services
-----------------lib
-----------------middleware
-----------------logs
-----------------docker
2. 使用yaml作为配置文件工具
yaml解析包
go get gopkg.in/yaml.v2
demo案例:
// 定义sturct来解析yaml
type Config struct{
Version string `yaml:"version"`
Items []string `yaml:"items"`
Spec struct {
Image struct{
Url string `yaml:"url"`
Name string `yaml:"name"`
} `yaml:"image"`
} `yaml:"spec"`
}
// config.yaml文件内容
version: "1.0"
items:
- "a"
- "b"
- "c"
spec:
image:
url: "www.baidu.com"
name: "baidu"
// 解析代码案例
var config Config
err = yaml.Unmarshal(strByte, &config)
if err != nil {
fmt.Println("yaml解析失败: " + err.Error())
}