【持续更新】全栈工程师学习笔记

互联网软件开发技术与实践学习笔记

本笔记是结合本课程中我所在小组完成的项目MeTube整个开发、测试、部署流程来撰写的。本笔记源码的GitHub地址为:https://github.com/wsxst/MyWebProjectNote/blob/master/%E4%BA%92%E8%81%94%E7%BD%91%E8%BD%AF%E4%BB%B6%E5%BC%80%E5%8F%91%E6%8A%80%E6%9C%AF%E4%B8%8E%E5%AE%9E%E8%B7%B5%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0.md。

前端

本笔记中前端内容主要涉及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                     // 项目基本信息,包依赖信息等
  1. main.js 程序入口文件,加载各种公共组件

第一部分(import)导入要用到的组件
第二部分新建了一个Vue对象,组件里面写的是App,也就是说导入 App.vue,在入口的时候将app的东西都显示出来。

  1. 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的变量语法,数组语法,对象语法:
  1. v-bind通常用来绑定属性的,格式是v-bind:属性名 = “值”,简写:属性名 = “值”
  2. 变量语法:v-bind:class = “变量”,变量形式 ,这里的变量的值,通常是在css定义好的类名;
  3. 数组语法:v-bind:class= “[变量1,变量2]” ,数组形式,其实跟上面差不多,只不过可以同时绑定多个class名;
  4. 对象语法: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.firstNamevm.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的值有两种,一种是字符串数组,一种是对象
  • 可以使用v­bind动态绑定父组件来的内容
    <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

在组件中使用v­model其实是一个语法糖,这背后其实做了两个操作:利用v-bind绑定一个 value属性,然后利用v-on指令给当前元素绑定input事件。

$emit(‘input’,value)的代码,这行代码实际上会触发一个input事件, ‘input’后的参数就是传递给v­model进行绑定。

<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/[email protected]  # 安装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())
}
发布了22 篇原创文章 · 获赞 0 · 访问量 1280

猜你喜欢

转载自blog.csdn.net/qq_35238352/article/details/103965851