go web 读书笔记

使用的导入的包

go get -u github.com/cihub/seelog

 

go 基础语法

http://www.runoob.com/go/go-tutorial.html

 

常量,变量 分组声明

 

分组声明
Go语言中,同时声明多个常量、变量,或者导入多个包时,可采用分组的方式进行声明。
例如下面的代码:
import "fmt"
import "os"
const i = 100
const pi = 3.1415
const prefix = "Go_"
var i int
var pi float32
var prefix string
可以分组写成如下形式:
import(
"fmt"
45
"os"
) c
onst(
i = 100
pi = 3.1415
prefix = "Go_"
) v
ar(
i int
pi float32
prefix string
)
除非被显式设置为其它值或iota,每个const分组的第一个常量被默认设置为它的0值,第二及后续的常量被默认
设置为它前面那个常量的值,如果前面那个常量的值是iota,则它也被设置为iota
iota枚举
Go里面有一个关键字iota,这个关键字用来声明enum的时候采用,它默认开始值是0,每调用一次加1
const(
x = iota // x == 0
y = iota // y == 1
z = iota // z == 2
w // 常量声明省略值时,默认和之前一个值的字面相同。这里隐式地说w = iota,因此w == 3。其实上面yz可同样不用
) c
onst v = iota // 每遇到一个const关键字,iota就会重置,此时v == 0

Go程序设计的一些规则

 

Go之所以会那么简洁,是因为它有一些默认的行为:

 

- 大写字母开头的变量是可导出的,也就是其它包可以读取的,是公用变量;小写字母开头的就是不可导出的,是私有变量。

- 大写字母开头的函数也是一样,相当于class中的带public关键词的公有函数;小写字母开头的就是有private关键词的私有函数。

 

反射
Go语言实现了反射,所谓反射就是动态运行时的状态。我们一般用到的包是reflect包。如何运用reflect包,官方的
这篇文章详细的讲解了reflect包的实现原理,laws of reflection
使用reflect一般分成三步,下面简要的讲解一下:

要去反射是一个类型的值(这些值都实现了空interface)

首先需
要把它转化成reflect对象(reflect.Type或者reflect.Value,根据不同的情况调用不同的函数)。这两种获取方式如
下:
t := reflect.TypeOf(i) //得到类型的元数据,通过t我们能获取类型定义里面的所有元素
v := reflect.ValueOf(i) //得到实际的值,通过v我们获取存储在里面的值,还可以去改变值
转化为reflect对象之后我们就可以进行一些操作了,也就是将reflect对象转化成相应的值,例如
tag := t.Elem().Field(0).Tag //获取定义在struct里面的标签
name := v.Elem().Field(0).String() //获取存储在第一个字段里面的值
获取反射值能返回相应的类型和数值
var  x  float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type())
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
fmt.Println("value:", v.Float())
最后,反射的话,那么反射的字段必须是可修改的,我们前面学习过传值和传引用,这个里面也是一样的道理,反射
的字段必须是可读写的意思是,如果下面这样写,那么会发生错误
var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1)
如果要修改相应的值,必须这样写

var  x  float64 = 3.4
p := reflect.ValueOf(&x)
v := p.Elem()
v.SetFloat(7.1)
上面只是对反射的简单介绍,更深入的理解还需要自己在编程中不断的实践

2.5 面向对象
前面两章我们介绍了函数和struct,那你是否想过函数当作struct的字段一样来处理呢?今天我们就讲解一下函数的
另一种形态,带有接收者的函数我们称为method

 

值传递,引用传递

6.2.1 按值传递(call by value) 按引用传递(call by reference)

Go 默认使用按值传递来传递参数,也就是传递参数的副本。函数接收参数副本之后,在使用变量的过程中可能对副本的值进行更改,但不会影响到原来的变量,比如 Function(arg1)。

 

如果你希望函数可以直接修改参数的值,而不是对参数的副本进行操作,你需要将参数的地址(变量名前面添加&符号,比如 &variable)传递给函数,这就是按引用传递,比如 Function(&arg1),此时传递给函数的是一个指针。如果传递给函数的是一个指针,指针的值(一个地址)会被复制,但指针的值所指向的地址上的值不会被复制;我们可以通过这个指针的值来修改这个值所指向的地址上的值。(译者注:指针也是变量类型,有自己的地址和值,通常指针的值指向一个变量的地址。所以,按引用传递也是按值传递。)

 

几乎在任何情况下,传递指针(一个32位或者64位的值)的消耗都比传递副本来得少。

 

在函数调用时,像切片(slice)、字典(map)、接口(interface)、通道(channel)这样的引用类型都是默认使用引用传递(即使没有显式的指出指针)

 

有些函数只是完成一个任务,并没有返回值。我们仅仅是利用了这种函数的副作用,就像输出文本到终端,发送一个邮件或者是记录一个错误等。

 

但是绝大部分的函数还是带有返回值的。

 

如下,simple_function.go 里的 MultiPly3Nums 函数带有三个形参,分别是 a、b、c,还有一个 int 类型的返回值(被注释的代码具有和未注释部分同样的功能,只是多引入了一个本地变量):

 

示例 6.2 simple_function.go

 

package main

 

import "fmt"

 

func main() {

    fmt.Printf("Multiply 2 * 5 * 6 = %d\n", MultiPly3Nums(2, 5, 6))

    // var i1 int = MultiPly3Nums(2, 5, 6)

    // fmt.Printf("MultiPly 2 * 5 * 6 = %d\n", i1)

}

 

func MultiPly3Nums(a int, b int, c int) int {

    // var product int = a * b * c

    // return product

    return a * b * c

}

输出显示:

 

Multiply 2 * 5 * 6 = 60

如果一个函数需要返回四到五个值,我们可以传递一个切片给函数(如果返回值具有相同类型)或者是传递一个结构体(如果返回值具有不同的类型)。因为传递一个指针允许直接修改变量的值,消耗也更少。

 

问题 6.2:

 

如下的两个函数调用有什么不同:

 

(A) func DoSomething(a *A) {

        b = a

    }

 

(B) func DoSomething(a A) {

        b = &a

}

goroutine
 

goroutineGo并行设计的核心。goroutine说到底其实就是线程,但是他比线程更小,十几个goroutine可能体现在底层就是五六个线程,Go语言内部帮你实现了这些goroutine之间的内存共享。执行goroutine只需极少的栈内存(大概是4~5KB),当然会根据相应的数据伸缩。也正因为如此,可同时运行成千上万个并发任务。

goroutinethread更易用、更高效、更轻便。
goroutine是通过Goruntime管理的一个线程管理器。

goroutine通过go关键字实现了,其实就是一个普通的函数。

RangeClose

记住应该在生产者的地方关闭channel,而不是消费的地方去关闭它,这样容易引起panic

Select
我们上面介绍的都是只有一个channel的情况,那么如果存在多个channel的时候,我们该如何操作呢,Go里面提供了
一个关键字select,通过select可以监听channel上的数据流动。

select默认是阻塞的,只有当监听的channel中有发送或接收可以进行时才会运行,当多个channel都准备好的时候,select是随机的选择一个执行的。

 

select里面还有default语法,select其实就是类似switch的功能,default就是当监听的channel都没有准备好的时候,默认执行的(select不再阻塞等待channel)。

 

超时
有时候会出现goroutine阻塞的情况,那么我们如何避免整个的程序进入阻塞的情况呢?我们可以利用select来设置
超时,通过如下的方式实现:
func main() {
c := make(chan int)
o := make(chan bool)
go func() {
for {
select {
case v := <- c:
println(v)
case <- time.After(5 * time.Second):
println("timeout")
o <- true
break
}
}
}()
<- o
}

 

runtime goroutine
runtime包中有几个处理goroutine的函数:
Goexit
退出当前执行的goroutine,但是defer函数还会继续调用
Gosched
让出当前goroutine的执行权限,调度器安排其他等待的任务运行,并在下次某个时候从该位置恢复执行。
NumCPU
返回 CPU 核数量
NumGoroutine
返回正在执行和排队的任务总数
GOMAXPROCS
用来设置可以运行的CPU核数

 

2.8 总结
这一章我们主要介绍了Go语言的一些语法,通过语法我们可以发现Go是多么的简单,只有二十五个关键字。让我们再
来回顾一下这些关键字都是用来干什么的。
 

break

default

func

interface

select

case

defer

go

map

struct

chan

else

goto

package

switch

const

fallthrough

if

range

type

continue

for

import

return

var

varconst参考2.2Go语言基础里面的变量和常量申明
packageimport已经有过短暂的接触
func 用于定义函数和方法
return 用于从函数返回
defer 用于类似析构函数
go 用于并行
select 用于选择不同类型的通讯
interface 用于定义接口,参考2.6小节
struct 用于定义抽象数据类型,参考2.5小节
breakcasecontinueforfallthroughelseifswitchgotodefault这些参考2.3流程介绍里面
chan用于channel通讯
type用于声明自定义类型
map用于声明map类型数据
range用于读取slicemapchannel数据
上面这二十五个关键字记住了,那么Go你也已经差不多学会了。

 

 

 

 

 

《Go语言标准库》The Golang Standard Library by Example

 

https://books.studygolang.com/The-Golang-Standard-Library-by-Example/

 

go环境配置

 

goIDEA环境配置

 

go  helloworld

 

Go 基本命令

 

  go build 命令主要是用于测试编译。在包的编译过程中,若有必要,会同时编译与之相关联的包。

 

  go run hello.go

 

  go get 命令主要是用来动态获取远程代码包的。

 

    go get github.com/go-sql-driver/mysql

 

  go run 命令主要用于编译并运行Go程序。

 

    go run hello.go

 

  go test 命令,会自动读取源码目录下面名为*_test.go的文件,生成并运行测试用的可执行文件

 

 

go  api

 

https://studygolang.com/pkgdoc

 

go  小demo

 

https://blog.csdn.net/mhw828/article/details/80703706

 

iota

package main

 

import "fmt"

 

func main() {

 

    //为 0

    const a = iota

    const (

        //每增加一行+1

        b = iota

        c = iota

        d = iota

 

        e, f = iota, iota

        // 同一行数值相等

        g, h, i, j, k = iota, iota, iota, iota, iota

    )

    fmt.Printf("a = %d , b = %d , c = %d , d = %d , e = %d , f = %d , g = %d , h = %d , i = %d , j = %d , k = %d ", a, b, c, d, e, f, g, h, i, j, k)

    //每次 const 出现时,都会让 iota 初始化为0.

    const l = iota

    fmt.Println(l)

 

}

 

const (

    a = iota

    b

    c

    // 使用 "_" 可以跳过值

    _

    _

    d

)

 

函数作为返回值

package main

 

import "fmt"

 

func main() {

 

    hello := func() {

        println("hello")

 

    }

    hello()

    //打印类型

    fmt.Printf(reflet.Typeof(hello))

}

 

Web服务

package main

 

import (

    "fmt"

    "log"

    "net/http"

)

 

func main() {

http.HandleFunc("/", handler) // each request calls handler

不可以带/

    log.Fatal(http.ListenAndServe("localhost:9512", nil))

}

 

// handler echoes the Path component of the request URL r.

func handler(w http.ResponseWriter, r *http.Request) {

    fmt.Fprintf(w, "你是我的眼")

}

Http请求

func main() {

 

//生成请求的客户端

client := &http.Client{}

//请求对象的URL
url := "http://baidu.com"

//生成请求的参数
 method 参数一定要大写
request ,_:=http.NewRequest("GET",url,nil)

//通过客户端把请求的参数往服务器发起请求
response,_:=client.Do(request)

//获取服务器返回的数据
body:=response.Body

//通过标准输出,把返回的数据输出来
stdout := os.Stdout
io.Copy(stdout,body)

//输出请求的状态

fmt.Println(response.Status)

 

}

 

 

原子计数器

package main

 

import "fmt"

import "time"

import "sync/atomic"

import "runtime"

 

func main() {

    // 我们使用一个无符号整型来代表一个永远为正整数的counter

    var ops uint64 = 0

    // 为了模拟并行更新,我们使用50个协程来每隔1毫秒来

    // 增加一下counter值,注意这里的50协程里面的for循环,

    // 也就是说如果主协程不退出,这些协程将永远运行下去

    // 所以这个程序每次输出的值有可能不一样

    for i := 0; i < 50; i++ {

        go func() {

            for {

                // 为了能够保证counter值增加的原子性,我们使用

                // atomic包中的AddUint64方法,将counter的地址和

                // 需要增加的值传递给函数即可

                atomic.AddUint64(&ops, 1)

                // 允许其他的协程来处理

                runtime.Gosched()

            }

        }()

    }

    //等待1秒中,让协程有时间运行一段时间

    time.Sleep(time.Second)

    // 为了能够在counter仍被其他协程更新值的同时安全访问counter值,

    // 我们获取一个当前counter值的拷贝,这里就是opsFinal,需要把

    // ops的地址传递给函数`LoadUint64`

    opsFinal := atomic.LoadUint64(&ops)

    fmt.Println("ops:", opsFinal)

}

 

 

信号

package main

import "fmt"

import "os"

import "os/signal"

import "syscall"

func main() {

    // Go信号通知通过向一个channel发送``os.Signal`来实现。

    // 我们将创建一个channel来接受这些通知,同时我们还用

    // 一个channel来在程序可以退出的时候通知我们

    sigs := make(chan os.Signal, 1)

    done := make(chan bool, 1)

    // `signal.Notify`在给定的channel上面注册该channel

    // 可以接受的信号

    signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)

    // 这个goroutine阻塞等待信号的到来,当信号到来的时候,

    // 输出该信号,然后通知程序可以结束了

    go func() {

        sig := <-sigs

        fmt.Println()

        fmt.Println(sig)

        done <- true

    }()

    // 程序将等待接受信号,然后退出

    fmt.Println("awaiting signal")

    <-done

    fmt.Println("exiting")

}

 

 

chan

package main

 

import (

    "fmt"

)

 

func main() {

 

    str1 := make(chan int, 2)

    str2 := make(chan int, 2)

 

    str1 <- 100

    str2 <- 0xff

 

    c1 := <-str1

    c2 := <-str2

 

    fmt.Printf("%d   %d", c1, c2)

}

 

 

空interface

空interface(interface{})不包含任何的method,正因为如此,所有的类型都实现了空interface。空interface对于描述起不到任何的作用(因为它不包含任何的method),但是空interface在我们需要存储任意类型的数值的时候相当有用,因为它可以存储任意类型的数值。它有点类似于C语言的void*类型。

// 定义a为空接口
var a interface{}
var i int = 5
s := "Hello world"
// a可以存储任意类型的数值
a = i
a = s

一个函数把interface{}作为参数,那么他可以接受任意类型的值作为参数,如果一个函数返回interface{},那么也就可以返回任意类型的值。是不是很有用啊!

 

go的mysql数据库驱动

MySQL驱动

Go中支持MySQL的驱动目前比较多,有如下几种,有些是支持database/sql标准,而有些是采用了自己的实现接口,常用的有如下几种:

  • https://github.com/go-sql-driver/mysql 支持database/sql,全部采用go写。
  • 这个驱动比较新,维护的比较好
  • 完全支持database/sql接口
  • 支持keepalive,保持长连接,虽然星星fork的mymysql也支持keepalive,但不是线程安全的,这个从底层就支持了keepalive。
  •  

示例代码

接下来的几个小节里面我们都将采用同一个数据库表结构:数据库test,用户表userinfo,关联用户信息表userdetail。

CREATE TABLE `userinfo` (
    `uid` INT(10) NOT NULL AUTO_INCREMENT,
    `username` VARCHAR(64) NULL DEFAULT NULL,
    `departname` VARCHAR(64) NULL DEFAULT NULL,
    `created` DATE NULL DEFAULT NULL,
    PRIMARY KEY (`uid`)
)
 
CREATE TABLE `userdetail` (
    `uid` INT(10) NOT NULL DEFAULT '0',
    `intro` TEXT NULL,
    `profile` TEXT NULL,
    PRIMARY KEY (`uid`)
)

如下示例将示范如何使用database/sql接口对数据库表进行增删改查操作

package main
 
import (
    _ "github.com/go-sql-driver/mysql"
    "database/sql"
    "fmt"
    //"time"
)
 
func main() {
    db, err := sql.Open("mysql", "astaxie:astaxie@/test?charset=utf8")
    checkErr(err)
 
    //插入数据
    stmt, err := db.Prepare("INSERT userinfo SET username=?,departname=?,created=?")
    checkErr(err)
 
    res, err := stmt.Exec("astaxie", "研发部门", "2012-12-09")
    checkErr(err)
 
    id, err := res.LastInsertId()
    checkErr(err)
 
    fmt.Println(id)
    //更新数据
    stmt, err = db.Prepare("update userinfo set username=? where uid=?")
    checkErr(err)
 
    res, err = stmt.Exec("astaxieupdate", id)
    checkErr(err)
 
    affect, err := res.RowsAffected()
    checkErr(err)
 
    fmt.Println(affect)
 
    //查询数据
    rows, err := db.Query("SELECT * FROM userinfo")
    checkErr(err)
 
    for rows.Next() {
        var uid int
        var username string
        var department string
        var created string
        err = rows.Scan(&uid, &username, &department, &created)
        checkErr(err)
        fmt.Println(uid)
        fmt.Println(username)
        fmt.Println(department)
        fmt.Println(created)
    }
 
    //删除数据
    stmt, err = db.Prepare("delete from userinfo where uid=?")
    checkErr(err)
 
    res, err = stmt.Exec(id)
    checkErr(err)
 
    affect, err = res.RowsAffected()
    checkErr(err)
 
    fmt.Println(affect)
 
    db.Close()
 
}
 
func checkErr(err error) {
    if err != nil {
        panic(err)
    }
}

通过上面的代码我们可以看出,Go操作Mysql数据库是很方便的。

关键的几个函数我解释一下:

sql.Open()函数用来打开一个注册过的数据库驱动,go-sql-driver中注册了mysql这个数据库驱动,第二个参数是DSN(Data Source Name),它是go-sql-driver定义的一些数据库链接和配置信息。它支持如下格式:

user@unix(/path/to/socket)/dbname?charset=utf8
user:password@tcp(localhost:5555)/dbname?charset=utf8
user:password@/dbname
user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname

db.Prepare()函数用来返回准备要执行的sql操作,然后返回准备完毕的执行状态。

db.Query()函数用来直接执行Sql返回Rows结果。

stmt.Exec()函数用来执行stmt准备好的SQL语句

 

NOSQL

5.6 NOSQL数据库操作

由 YmKK 创建, 最后一次修改 2016-02-24

NoSQL(Not Only SQL),指的是非关系型的数据库。随着Web2.0的兴起,传统的关系数据库在应付Web2.0网站,特别是超大规模和高并发的SNS类型的Web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。

而Go语言作为21世纪的C语言,对NOSQL的支持也是很好,目前流行的NOSQL主要有redis、mongoDB、Cassandra和Membase等。这些数据库都有高性能、高并发读写等特点,目前已经广泛应用于各种应用中。我接下来主要讲解一下redis和mongoDB的操作。

redis

redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)和zset(有序集合)。

目前应用redis最广泛的应该是新浪微博平台,其次还有Facebook收购的图片社交网站instagram。以及其他一些有名的互联网企业

目前我fork了最后一个驱动,更新了一些bug,目前应用在我自己的短域名服务项目中(每天200W左右的PV值)

https://github.com/astaxie/goredis

 

 

接下来的以我自己fork的这个redis驱动为例来演示如何进行数据的操作

package main
 
import (
    "github.com/astaxie/goredis"
    "fmt"
)
 
func main() {
    var client goredis.Client
    // 设置端口为redis默认端口
    client.Addr = "127.0.0.1:6379"
 
    //字符串操作
    client.Set("a", []byte("hello"))
    val, _ := client.Get("a")
    fmt.Println(string(val))
    client.Del("a")
 
    //list操作
    vals := []string{"a", "b", "c", "d", "e"}
    for _, v := range vals {
        client.Rpush("l", []byte(v))
    }
    dbvals,_ := client.Lrange("l", 0, 4)
    for i, v := range dbvals {
        println(i,":",string(v))
    }
    client.Del("l")
}

我们可以看到操作redis非常的方便,而且我实际项目中应用下来性能也很高。client的命令和redis的命令基本保持一致。所以和原生态操作redis非常类似。

 

 

第六章 session和数据存储

Web里面经典的解决方案是cookie和session,cookie机制是一种客户端机制,把用户数据保存在客户端,而session机制是一种服务器端的机制,服务器使用一种类似于散列表的结构来保存信息,每一个网站访客都会被分配给一个唯一的标志符,即sessionID,它的存放形式无非两种:要么经过url传递,要么保存在客户端的cookies里.当然,你也可以将Session保存到数据库里,这样会更安全,但效率方面会有所下降

go main 文件的引用 Init

 

go增删改查

 

go models  实体与数据库的映射

https://blog.csdn.net/boss2967/article/details/82792076

beego  环境配置

Beego环境搭建和bee工具安装使用,以Windows环境为例。

 

首先,下载并安装好GO并配置好GOROOT和GOPATH环境变量(如果您是用msi包安装的go,那么这些环境变量已经设置好了)。并在Path环境变量中加入%GOPATH%\bin和%GOROOT%bin。

 

第二步,下载并安装好git bash工具。

 

第三步,打开gitbash,输入 go get github.com/astaxie/beego  。稍等片刻即可在GOPATH的src目录下看到有\github.com\astaxie\beego目录。

 

第四步,在gitbash中输入 go get github.com/beego/bee  。稍等片刻即可在GOPATH的src目录下看到有\github.com\beego\bee目录,同时有很多依赖包被下载添加到\github.com目录下。

 

测试bee是否安装成功,可在命令行中输入bee,得到如下结果就成功了。

第五步:使用bee工具生成beego框架工程代码。在这一步之前一定要确定在PATH环境变量里已经加入了%GOPATH%\bin,里面有bee.exe。然后在开始菜单中找到命令提示符,以管理员身份运行(或者在git bash工具中进入到GOPATH的src目录下,注意必须到这个目录下),再输入 bee new <工程名称>,这样一个beego框架的工程就生成成功了。

 

 

beego 创建 web 项目 ,api项目

然后在开始菜单中找到命令提示符,以管理员身份运行(或者在git bash工具中进入到GOPATH的src目录下,注意必须到这个目录下),再输入 bee new <工程名称>,这样一个beego框架的工程就生成成功了。

//web 项目

Bee new project

// bee  api  项目 

Bee api project

 

beego blog 项目

 

beego项目交叉编译 部署到linux环境运行

go  项目编译 linux 运行

在xx.go所在的的文件夹下按sheet+鼠标右键在dos下打开,执行下面的命令

SET CGO_ENABLED=0

set GOARCH=amd64

set GOOS=linux

GOOS:目标平台的操作系统(darwin、freebsd、linux、windows)

GOARCH:目标平台的体系架构(386、amd64、arm)

交叉编译不支持 CGO 所以要禁用它

上面的命令编译 64 位可执行程序,你当然应该也会使用 386 编译 32 位可执行程序

go build xx.go

会生成一个没有后缀的xx二进制文件

并把项目 conf,static,views文件夹一起打包

将该文件放入linux系统某个文件夹下

赋予权限

chmod 777 xx

执行

./xx

 

 

 

 

beego项目编译运行linux环境

打开Terminal 定位到工程的 main.go 文件夹目录

 

使用命令 bee pack -be GOOS=linux 进行打包

 

打包成Windows 命令 bee pack -be GOOS=windows

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值