init函数
init函数就是在程序执行之前被调用的函数,不需要传入参数也没有返回值,而且init函数是不能被其他函数调用的。
init函数的作用:
- 变量初始化
- 检查和修复程序状态
- 运行前注册,例如decoder,parser的注册
- 运行只需计算一次的模块,像sync.once的作用
- 其他
init函数执行顺序
这是引用多个包,不同包之间init函数执行的顺序。当同一个包中不同的文件有多个init函数时,则按文件的字典序进行初始化;若一个文件有多个init函数,则按函数的位置从上到下进行执行。
执行顺序总结:从当前包开始,如果当前包包含多个依赖包,则先初始化依赖包,层层递归初始化各个包,在每一个包中,按照源文件的字典序从前往后执行,每一个源文件中,优先初始化常量、变量,最后初始化init函数,当出现多个init函数时,则按照顺序从前往后依次执行,每一个包完成加载后,递归返回,最后在初始化当前包!
小例子:
var a = first()
func init() {
fmt.Println("this is init function")
}
func main() {
fmt.Println("this is main function")
}
func first() int {
fmt.Println("我比init先初始化")
return 0
}
输出:
有时候我们在导入包前加_表示只是想执行导入包中的init函数。
import _ "image/png"
image/png包里的init函数作用是向image包注册png图片的解码器,见src/image/png/reader.go
常用的init()作用是初始化在函数变量申明时无法初始化的变量:
var square [10]int
func init() {
//fmt.Println("this is init function")
for i := 0; i < 10; i++ {
square[i] = i * i
}
}
error 减少erro的技巧:
当程序中要多次调用一个函数并多次处理返回的error时可以将error与方法调用包装起来,避免重复error处理。
type wrapWrite struct {
w io.Writer
err error
}
func (w *wrapWrite) write(bytes []byte) {
if w.err != nil {
return
}
_,w.err = w.w.Write(bytes)
}
func (w *wrapWrite) getErr()error {
return w.err
}
func main() {
s1 := "123"
s2 := "456"
newWrite := &wrapWrite{}
newWrite.write([]byte(s1))
newWrite.write([]byte(s2))
if newWrite.getErr() != nil {
//do something
}
}
panic:
- go中一旦使用的panic,就是想要程序终止。
- 虽然go有recover的机制来恢复panic,但是在写panic时,不能假设调用者会使用recover。这和try-catch-exception机制中,抛出的exception可认为调用者有义务try-catch的思想是不同的。
- 在写一般函数方法时,如果不是类似索引越界,栈内存溢出等无法恢复的错误,尽量不去使用panic。
- 在项目启动时,资源初始化如加载配置文件、数据库连接等这种一旦失败了,整个项目没有进行下去的意义时,会使用panic。但是也有一些被弱依赖的资源初始化,可能会有一些不同,这要根据项目的实际情况来定。 recover:
- 一般来说,出现了panic就不需要再救了。
- 但在类似web服务的框架中,不能由于某一个请求中出现了panic而导致整个服务gg。所以在框架层面会在每一个请求调用链的最上游统一做recover。 当recover和panic未在一个携程中时就不起作用了
func do() {
panic(errors.New("panic in do"))
}
func DoSomething() {
defer func() {
if err := recover();err != nil {
// handle
log.Printf("panic被recover了 %v",err)
}
}()
// 无法捕获新启动的协程
go do()
}
session和cookie
cookie,就是在本地计算机保存一些用户操作的历史信息(当然包括登录信息),并在用户再次访问该站点时浏览器通过HTTP协议将本地cookie内容发送给服务器,从而完成验证,或继续上一步操作。 流程:
session,就是在服务器上保存用户操作的历史信息。服务器使用session id来标识session,session id由服务器负责产生,保证随机性与唯一性,相当于一个随机密钥,避免在握手或传输中暴露用户真实密码。但该方式下,仍然需要将发送请求的客户端与session进行对应,所以可以借助cookie机制来获取客户端的标识(即session id),也可以通过GET方式将id提交给服务器。
seesionId可以有两种方式存放方式:
- 经过url传递
- 放在cookie里面
hst框架中的session
Session接口中定义了对session的操作,框架中有两种该接口的实现方式,一个是session存储在内存中,一个是session存储在文件中。
在sessionMemory中定义了SessionMemory结构体来存储每个用户对应的Session
type SessionMemory struct {
cookieName string
cookiePath string
cookieDomain string
cookieExpire time.Duration
lock sync.RWMutex
data map[string]*map[string]*sessionData //存储session信息 第一个string为cookie标识
maxExpire time.Duration // 文件过期时间
}
新建全局SeesionMemory时,新开一个协程来清除保存在SessionMemory中的过期session
set方法流程:
- 查看请求中是否已经存在生成的cookie
- 若不存在,新生成一个cookie填充进请求中,生成的value表示seesionId,对应用户访问的保存在服务端的信息
- 若存在,则更新sesrrion中数据的过期时间;不存在则将key,value保存进sessionData中
get方法流程:
- 根据cookieName获取请求中cookie,再根据value和key获取sesionData中保存的数据
- 如果不存在该cookie则直接返回,未找到key对应SeesionData也返回
销毁当前访问用户存储在SessionMemory中的Session
Hst框架在context中对session操作进行了进一步封装。
简单小例子:登录操作
初始化路由:
h := hst.New(nil)
//初始化session
h.SetSession(hst.NewSessionMemory("", "/", "HST_SESSION", time.Minute))
h.HandleFunc("/addUser", controller.CreateController)
h.HandleFunc("/getUserList", controller.GetUserList)
h.RegisterHandle(nil, &controller.User{})
handler:
func (user *User) LoginCheck(c *hst.Context) {
c.R.ParseForm()
username := c.R.FormValue("name")
password := c.R.FormValue("password")
quesUser, err := service.GetUserByName(username)
if err != nil {
c.JSON(http.StatusBadRequest, err)
}
if quesUser.Password != password {
c.JSON(http.StatusBadRequest, "用户密码错误")
}
//登录操作成功 用户信息写入session 返回给客户端
c.SessionSet("userInfo", user)
c.JSON(http.StatusOK, struct { //常见错误
Id int
Name string
Msg string
}{
Id: quesUser.Id,
Name: quesUser.Name,
Msg: "登录成功",
})
//c.JSON2(http.StatusOK, 0, quesUser)
}
问题: 实际中session的应用场景? 该怎么用?
今日踩坑
当用该函数返回结果时,第二个参数中的struct中字段的首字符没有大写,导致后面将该数据解析为json数据时,解析为空,且不会报错。 要进行marshaled/unmarshaled,结构体的字段必须被导出(定义为公有)