这篇文章是根据课程 Go 开发短地址服务 做的笔记
项目地址:github.com/astak16/sho…
过错处理
在处理业务逻辑时,如果出过错了,需求一致处理过错呼应的格式,这样能够便利前端处理过错信息
所以需求界说一个 Error
接口,它包含了 error
接口,以及一个 Status()
办法,用来回来过错的状况码
type Error interface {
error
Status() int
}
这个接口用来判别过错类型,在 go
中能够经过 e.(type)
判别过错的类型
func respondWithError(w http.RespondWrite, err error) {
switch e.(type) {
case Error:
respondWithJSON(w, e.Status(), e.Error())
default:
respondWithJSON(w, http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
}
在 go
中 完成 Error
接口,只需求完成 Error()
和 Status()
办法即可
func () Error() string {
return ""
}
func () Status() int {
return 0
}
这样界说的办法,只能回来固定的文本和状况码,如果想要回来动态内容,能够界说一个结构体
然后 Error
和 Status
办法承受 StatusError
类型
这样只需满足 StatusError
类型的结构体,就能够回来动态内容
所以上面的代码能够修改为:
type StatusError struct {
Code int
Err error
}
func (se StatusError) Error() string {
return se.Err.Error()
}
func (se StatusError) Status() int {
return se.Code
}
middlerware
RecoverHandler
中间件 RecoverHandler
作用是经过 defer
来捕获 panic
,然后回来 500
状况码
func RecoverHandler(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
defer func() {
if r := recover(); r != nil {
log.Println("Recover from panic %+v", r)
http.Error(w, http.StatusText(500), 500)
}
}()
next.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
LoggingHandler
LoggingHandler
作用是记载恳求耗时
func (m Middleware) LoggingHandler(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
end := time.Now()
log.Printf("[%s] %q %v", r.Method, r.URL.Path, end.Sub(start))
}
return http.HandlerFunc(fn)
}
中间件运用
alice
是 go
中的一个中间件库,能够经过 alice.New()
来增加中间件,详细运用如下:
m := alice.New(middleware.LoggingHandler, middleware.RecoverHandler)
mux.Router.HandleFunc("/api/v1/user", m.ThenFunc(controller)).Methods("POST")
生成短链接
redis 衔接
func NewRedisCli(addr string, passwd string, db int) *RedisCli {
c := redis.NewClient(&redis.Options{
Addr: addr,
Password: passwd,
DB: db,
})
if _, err := c.Ping().Result(); err != nil {
panic(err)
}
return &RedisCli{Cli: c}
}
生成仅有 ID
redis
能够根据一个键名生成一个仅有的自增 ID,这个键名能够是任意的,这个办法是 Incr
代码如下:
err = r.Cli.Incr(URLIDKEY).Err()
if err != nil {
return "", err
}
id, err := r.Cli.Get(URLIDKEY).Int64()
if err != nil {
return "", err
}
fmt.Println(id) // 每次调用都会自增
存储和解析短链接
一个 ID
对应一个 url
,也就是说当外面传入 id
时需求回来对应的 url
func Shorten() {
err := r.Cli.Set(fmt.Sprintf(ShortlinkKey, eid), url, time.Minute*time.Duration(exp)).Err()
if err != nil {
return "", err
}
}
func UnShorten() {
url, err := r.Cli.Get(fmt.Sprintf(ShortlinkKey, eid)).Result()
}
redis 留意事项
redis
回来的 error
有两种情况:
-
redis.Nil
表明没有找到对应的值 - 其他过错,表明
redis
服务出错了
所以在运用 redis
时,需求判别回来的过错类型
if err == redis.Nil {
// 没有找到对应的值
} else if err != nil {
// redis 服务出错了
} else {
// 正确呼应
}
测验
在测验用例中,如何建议一个恳求,然后获取呼应的数据呢?
- 结构恳求
var jsonStr = []byte(`{"url":"https://www.baidu.com","expiration_in_minutes":60}`)
req, err := http.NewRequest("POST", "/api/shorten", bytes.NewBuffer(jsonStr))
if err != nil {
t.Fatal(err)
}
req.Header.Set("Content-Type", "application/json")
- 捕获
http
呼应
rw := httptest.NewRecorder()
- 模拟恳求被处理
app.Router.ServeHTTP(rw, req)
- 解析呼应
if rw.Code != http.ok {
t.Fatalf("Excepted status created, got %d", rw.Code)
}
resp := struct {
Shortlink string `json:"shortlink"`
}{}
if err := json.NewDecoder(rw.Body).Decode(&resp); err != nil {
t.Fatalf("should decode the response", err)
}
最终完好代码:
var jsonStr = []byte(`{"url":"https://www.baidu.com","expiration_in_minutes":60}`)
req, err := http.NewRequest("POST", "/api/shorten", bytes.NewBuffer(jsonStr))
if err != nil {
t.Fatal(err)
}
req.Header.Set("Content-Type", "application/json")
rw := httptest.NewRecorder()
app.Router.ServeHTTP(rw, req)
if rw.Code != http.ok {
t.Fatalf("Excepted status created, got %d", rw.Code)
}
resp := struct {
Shortlink string `json:"shortlink"`
}{}
if err := json.NewDecoder(rw.Body).Decode(&resp); err != nil {
t.Fatalf("should decode the response")
}
代码
log.SetFlags(log.LstdFlags | log.Lshortfile)
作用是设置日志输出的标志
它们都是标志常量,用竖线 |
衔接,这是位操作符,将他们合并为一个整数值,作为 log.SetFlags()
的参数
-
log.LstdFlags
是标准时间格式:2022-01-23 01:23:23
-
log.Lshortfile
是文件名和行号:main.go:23
当我们运用 log.Println
输出日志时,会主动带上时间、文件名、行号信息
recover 函数运用
recover
函数类似于其他语言的 try...catch
,用来捕获 panic
,做一些处理
运用办法:
func MyFunc() {
defer func() {
if r := recover(); r != nil {
// 处理 panic 情况
}
}
}
需求留意的是:
-
recover
函数只能在defer
中运用,如果在defer
之外运用,会直接回来nil
-
recover
函数只要在panic
之后调用才会生效,如果在panic
之前调用,也会直接回来nil
-
recover
函数只能捕获当前goroutine
的panic
,不能捕获其他goroutine
的panic
next.ServerHttp(w, r)
next.ServeHTTP(w, r)
,用于将 http
恳求传递给下一个 handler
HandleFunc 和 Handle 差异
HandleFunc
承受一个一般类型的函数:
func myHandle(w http.ResponseWriter, r *http.Request) {}
http.HandleFunc("xxxx", myHandle)
Handle
接纳一个完成 Handler
接口的函数:
func myHandler(w http.ResponseWriter, r *http.Request) {}
http.Handle("xxxx", http.HandlerFunc(myHandler))
他们的差异是:运用 Handle
需求自己进行包装,运用 HandleFunc
不需求
defer res.Body.Close()
为什么没有 res.Header.Close()
办法?
由于 header
不是资源,而 body
是资源,在 go
中,一般操作资源后,要及时关闭资源,所以 go
为 body
供给了 Close()
办法
res.Body
是 io.ReadCloser
类型的接口,表明能够读取呼应数据并关闭呼应体的目标
w.Write()
代码在履行了 w.Writer(res)
后,还会持续往下履行,除非有显现的 reture
和 panic
停止函数履行
func controller(w http.ResponseWriter, r *http.Request) {
if res, err := xxx; err != nil {
respondWithJSON(w, http.StatusOK, err)
}
// 这里如果有代码,会持续履行
}
func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) {
res, _ json.Marshal(payload)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
w.Write(res)
}
需求留意的是,虽然履行了 w.Writer()
后,还会持续往下履行,但不会再对呼应进行修改或写入任何内容了,由于 w.Write()
已经将呼应写入到 http.ResponseWriter
中了
获取恳求参数
路由 /api/info?shortlink=2
a.Router.Handle("/api/info", m.ThenFunc(a.getShortlinkInfo)).Methods("GET")
func getShortlinkInfo(w http.ResponseWriter, r *http.Request) {
vals := r.URL.Query()
s := vals.Get("shortlink")
fmt.Println(s) // 2
}
路由 /2
a.Router.Handle("/{shortlink:[a-zA-Z0-9]{1,11}}", m.ThenFunc(a.redirect)).Methods("GET")
func redirect(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
shortlink := vars["shortlink"]
fmt.Println(shortlink) // 2
}
获取恳求体
json.NewDecoder(r.Body)
作用是将 http
恳求的 body
内容解析为 json
格式
r.body
是一个 io.Reader
类型,它代表恳求的原始数据
如果关联成功能够用 Decode()
办法来解析 json
数据
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
func controller(w http.ResponseWriter, r *http.Request){
var user User
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
fmt.Println(err)
}
fmt.Println(user)
}
new
用于创立一个新的零值目标,并回来该目标的指针
它承受一个类型作为参数,并回来一个指向该类型的指针
适用于任何可分配的类型,如基本类型、结构体、数组、切片、映射和接口等
// 创立一个新的 int 类型的零值目标,并回来指向它的指针
ptr := new(int) // 0
需求留意的是:new
只分配了内存,并初始化为零值,并不会对目标进行任何进一步的初始化。如果需求对目标进行自界说的初始化操作,能够运用结构体字面量或结构函数等方法
往期文章
- Go 项目ORM、测验、api文档建立
- Go 完成一致加载资源的入口