Go实战--golang中使用echo框架、MongoDB、JWT搭建REST API(labstack/echo、gopkg.in/mgo.v2、dgrijalva/jwt-go)

生命不止,继续go go go !!!

之前介绍过golang中restful api的博客,是使用redis作为持久化,httprouter作为框架:
Go实战–通过httprouter和redis框架搭建restful api服务(github.com/julienschmidt/httprouter)

今天,继续echo框架,这次加入mongodb作为持久化存储,使用jwt进行验证,来搭建一套rest api,类似Twitter。

其中,很多知识点之前都有介绍过:
关于golang中使用mongodb科技参考:
Go实战–golang使用ssl连接MongoDB(mgo)

Go实战–golang中使用MongoDB(mgo)

关于golang中的使用jwt(JSON Web Token):
Go实战–golang中使用JWT(JSON Web Token)

代码结构:

./model
   post.go
   user.go
./handler
   handler.go
   post.go
   user.go
main.go

model

这里没有什么好说的,就是建立model,需要注意的就是golang中struct中的标签。
一个用户user,一个邮箱post。

user.go

package model

import (
    "gopkg.in/mgo.v2/bson"
)

type (
    User struct {
        ID        bson.ObjectId `json:"id" bson:"_id,omitempty"`
        Email     string        `json:"email" bson:"email"`
        Password  string        `json:"password,omitempty" bson:"password"`
        Token     string        `json:"token,omitempty" bson:"-"`
        Followers []string      `json:"followers,omitempty" bson:"followers,omitempty"`
    }
)

post.go

package model

import (
    "gopkg.in/mgo.v2/bson"
)

type (
    Post struct {
        ID      bson.ObjectId `json:"id" bson:"_id,omitempty"`
        To      string        `json:"to" bson:"to"`
        From    string        `json:"from" bson:"from"`
        Message string        `json:"message" bson:"message"`
    }
)

handler

handler.go
handler中提出出来的公共部分。

package handler

import (
    "gopkg.in/mgo.v2"
)

type (
    Handler struct {
        DB *mgo.Session
    }
)

const (
    // Key (Should come from somewhere else).
    Key = "secret"
)

post.go
post中加入两个功能,一个创建post,一个拉取post。
关于”net/http”可以参考:
Go语言学习之net/http包(The way to go)
关于”strconv”可以参考:
Go语言学习之strconv包(The way to go)

package handler

import (
    "go_echo_examples/twitter/model"
    "net/http"
    "strconv"

    "github.com/labstack/echo"
    "gopkg.in/mgo.v2"
    "gopkg.in/mgo.v2/bson"
)

func (h *Handler) CreatePost(c echo.Context) (err error) {
    u := &model.User{
        ID: bson.ObjectIdHex(userIDFromToken(c)),
    }
    p := &model.Post{
        ID:   bson.NewObjectId(),
        From: u.ID.Hex(),
    }
    if err = c.Bind(p); err != nil {
        return
    }

    // Validation
    if p.To == "" || p.Message == "" {
        return &echo.HTTPError{Code: http.StatusBadRequest, Message: "invalid to or message fields"}
    }

    // Find user from database
    db := h.DB.Clone()
    defer db.Close()
    if err = db.DB("twitter").C("users").FindId(u.ID).One(u); err != nil {
        if err == mgo.ErrNotFound {
            return echo.ErrNotFound
        }
        return
    }

    // Save post in database
    if err = db.DB("twitter").C("posts").Insert(p); err != nil {
        return
    }
    return c.JSON(http.StatusCreated, p)
}

func (h *Handler) FetchPost(c echo.Context) (err error) {
    userID := userIDFromToken(c)
    page, _ := strconv.Atoi(c.QueryParam("page"))
    limit, _ := strconv.Atoi(c.QueryParam("limit"))

    // Defaults
    if page == 0 {
        page = 1
    }
    if limit == 0 {
        limit = 100
    }

    // Retrieve posts from database
    posts := []*model.Post{}
    db := h.DB.Clone()
    if err = db.DB("twitter").C("posts").
        Find(bson.M{"to": userID}).
        Skip((page - 1) * limit).
        Limit(limit).
        All(&posts); err != nil {
        return
    }
    defer db.Close()

    return c.JSON(http.StatusOK, posts)
}

user.go
这部分包括用户注册、登录、添加follow。
关于time包可以参考:
Go语言学习之time包(获取当前时间戳等)(the way to go)

package handler

import (
    "go_echo_examples/twitter/model"
    "net/http"
    "time"

    "github.com/dgrijalva/jwt-go"
    "github.com/labstack/echo"
    "gopkg.in/mgo.v2"
    "gopkg.in/mgo.v2/bson"
)

func (h *Handler) Signup(c echo.Context) (err error) {
    // Bind
    u := &model.User{ID: bson.NewObjectId()}
    if err = c.Bind(u); err != nil {
        return
    }

    // Validate
    if u.Email == "" || u.Password == "" {
        return &echo.HTTPError{Code: http.StatusBadRequest, Message: "invalid email or password"}
    }

    // Save user
    db := h.DB.Clone()
    defer db.Close()
    if err = db.DB("twitter").C("users").Insert(u); err != nil {
        return
    }

    return c.JSON(http.StatusCreated, u)
}

func (h *Handler) Login(c echo.Context) (err error) {
    // Bind
    u := new(model.User)
    if err = c.Bind(u); err != nil {
        return
    }

    // Find user
    db := h.DB.Clone()
    defer db.Close()
    if err = db.DB("twitter").C("users").
        Find(bson.M{"email": u.Email, "password": u.Password}).One(u); err != nil {
        if err == mgo.ErrNotFound {
            return &echo.HTTPError{Code: http.StatusUnauthorized, Message: "invalid email or password"}
        }
        return
    }

    //-----
    // JWT
    //-----

    // Create token
    token := jwt.New(jwt.SigningMethodHS256)

    // Set claims
    claims := token.Claims.(jwt.MapClaims)
    claims["id"] = u.ID
    claims["exp"] = time.Now().Add(time.Hour * 72).Unix()

    // Generate encoded token and send it as response
    u.Token, err = token.SignedString([]byte(Key))
    if err != nil {
        return err
    }

    u.Password = "" // Don't send password
    return c.JSON(http.StatusOK, u)
}

func (h *Handler) Follow(c echo.Context) (err error) {
    userID := userIDFromToken(c)
    id := c.Param("id")

    // Add a follower to user
    db := h.DB.Clone()
    defer db.Close()
    if err = db.DB("twitter").C("users").
        UpdateId(bson.ObjectIdHex(id), bson.M{"$addToSet": bson.M{"followers": userID}}); err != nil {
        if err == mgo.ErrNotFound {
            return echo.ErrNotFound
        }
    }

    return
}

func userIDFromToken(c echo.Context) string {
    user := c.Get("user").(*jwt.Token)
    claims := user.Claims.(jwt.MapClaims)
    return claims["id"].(string)
}

main

最后的main.go就相对很简单了。
main.go

package main

import (
    "go_echo_examples/twitter/handler"

    "github.com/labstack/echo"
    "github.com/labstack/echo/middleware"
    "github.com/labstack/gommon/log"
    "gopkg.in/mgo.v2"
)

func main() {
    e := echo.New()
    e.Logger.SetLevel(log.ERROR)
    e.Use(middleware.Logger())
    e.Use(middleware.JWTWithConfig(middleware.JWTConfig{
        SigningKey: []byte(handler.Key),
        Skipper: func(c echo.Context) bool {
            // Skip authentication for and signup login requests
            if c.Path() == "/login" || c.Path() == "/signup" {
                return true
            }
            return false
        },
    }))

    // Database connection
    db, err := mgo.Dial("localhost")
    if err != nil {
        e.Logger.Fatal(err)
    }

    // Create indices
    if err = db.Copy().DB("twitter").C("users").EnsureIndex(mgo.Index{
        Key:    []string{"email"},
        Unique: true,
    }); err != nil {
        log.Fatal(err)
    }

    // Initialize handler
    h := &handler.Handler{DB: db}

    // Routes
    e.POST("/signup", h.Signup)
    e.POST("/login", h.Login)
    e.POST("/follow/:id", h.Follow)
    e.POST("/posts", h.CreatePost)
    e.GET("/feed", h.FetchPost)

    // Start server
    e.Logger.Fatal(e.Start(":1323"))
}

测试

启动mongodb服务

mongod.exe --dbpath d:\mongodb_data\db

成功的话,结果:

2017-11-27T00:17:22.201-0700 I CONTROL  [initandlisten] MongoDB starting : pid=17792 port=27017 dbpath=d:\mongodb_data\db 64-bit host=LAPTOP-MNU6522J
2017-11-27T00:17:22.202-0700 I CONTROL  [initandlisten] targetMinOS: Windows 7/Windows Server 2008 R2
2017-11-27T00:17:22.202-0700 I CONTROL  [initandlisten] db version v3.4.6
2017-11-27T00:17:22.203-0700 I CONTROL  [initandlisten] git version: c55eb86ef46ee7aede3b1e2a5d184a7df4bfb5b5
2017-11-27T00:17:22.203-0700 I CONTROL  [initandlisten] OpenSSL version: OpenSSL 1.0.1u-fips  22 Sep 2016
2017-11-27T00:17:22.204-0700 I CONTROL  [initandlisten] allocator: tcmalloc
2017-11-27T00:17:22.204-0700 I CONTROL  [initandlisten] modules: none
2017-11-27T00:17:22.204-0700 I CONTROL  [initandlisten] build environment:
2017-11-27T00:17:22.204-0700 I CONTROL  [initandlisten]     distmod: 2008plus-ssl
2017-11-27T00:17:22.205-0700 I CONTROL  [initandlisten]     distarch: x86_64
2017-11-27T00:17:22.205-0700 I CONTROL  [initandlisten]     target_arch: x86_64
2017-11-27T00:17:22.205-0700 I CONTROL  [initandlisten] options: { storage: { dbPath: "d:\mongodb_data\db" } }
2017-11-27T00:17:22.261-0700 I -        [initandlisten] Detected data files in d:\mongodb_data\db created by the 'wiredTiger' storage engine, so setting the active storage engine to 'wiredTiger'.
2017-11-27T00:17:22.271-0700 I STORAGE  [initandlisten] wiredtiger_open config: create,cache_size=3540M,session_max=20000,eviction=(threads_min=4,threads_max=4),config_base=false,statistics=(fast),log=(enabled=true,archive=true,path=journal,compressor=snappy),file_manager=(close_idle_time=100000),checkpoint=(wait=60,log_size=2GB),statistics_log=(wait=0),
2017-11-27T00:17:24.247-0700 I CONTROL  [initandlisten]
2017-11-27T00:17:24.248-0700 I CONTROL  [initandlisten] ** WARNING: Access control is not enabled for the database.
2017-11-27T00:17:24.259-0700 I CONTROL  [initandlisten] **          Read and write access to data and configuration is unrestricted.
2017-11-27T00:17:24.260-0700 I CONTROL  [initandlisten]
2017-11-27T15:17:25.037+0800 I FTDC     [initandlisten] Initializing full-time diagnostic data capture with directory 'd:/mongodb_data/db/diagnostic.data'
2017-11-27T15:17:25.046+0800 I NETWORK  [thread1] waiting for connections on port 27017

运行main.go
mongodb控制台:

2017-11-27T15:23:39.223+0800 I NETWORK  [thread1] connection accepted from 127.0.0.1:51150 #2 (1 connection now open)
2017-11-27T15:23:39.501+0800 I INDEX    [conn2] build index on: twitter.users properties: { v: 2, unique: true, key: { email: 1 }, name: "email_1", ns: "twitter.users" }
2017-11-27T15:23:39.501+0800 I INDEX    [conn2]          building index using bulk method; build may temporarily use up to 500 megabytes of RAM
2017-11-27T15:23:39.529+0800 I INDEX    [conn2] build index done.  scanned 0 total records. 0 secs
2017-11-27T15:23:39.530+0800 I COMMAND  [conn2] command twitter.$cmd command: createIndexes { createIndexes: "users", indexes: [ { name: "email_1", ns: "twitter.users", key: { email: 1 }, unique: true } ] } numYields:0 reslen:113 locks:{ Global: { acquireCount: { r: 1, w: 1 } }, Database: { acquireCount: { W: 1 } }, Collection: { acquireCount: { w: 1 } } } protocol:op_query 303ms

用户注册
使用postman或是curl命令,这里使用curl命令了:

curl -X POST http://localhost:1323/signup -H "Content-Type:application/json" -d '{"email" :"[email protected]"m", "password":"test"}'

登录

curl -X POST http://localhost:1323/login -H "Content-Type:application/json" -d '{"email" :"[email protected]", "password":"test"}'

返回:

{"id":"5a1bbe92271c7c5ac875e40e","email":"[email protected]","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MTIwMjcwOTgsImlkIjoiNWExYmJlOTIyNzFjN2M1YWM4NzVlNDBlIn0.V__5q0fipKfPhcGop1rDiOX5lFc7qSVz9bVfJ5zycvo"}

Follow用户

curl -X POST http://localhost:1323/follow/5a1bbe92271c7c5ac875e40e -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MTIwMjcwOTgsImlkIjoiNWExYmJlOTIyNzFjN2M1YWM4NzVlNDBlIn0.V__5q0fipKfPhcGop1rDiOX5lFc7qSVz9bVfJ5zycvo"

发送消息(邮件)

curl -X POST http://localhost:1323/posts -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MTIwMjcwOTgsImlkIjoiNWExYmJlOTIyNzFjN2M1YWM4NzVlNDBlIn0.V__5q0fipKfPhcGop1rDiOX5lFc7qSVz9bVfJ5zycvo" -H "Content-Type: application/json" -d '{"to":"58465b4ea6fe886d3215c6df","message":"hello"}'

Go实战--golang中使用echo框架、MongoDB、JWT搭建REST API(labstack/echo、gopkg.in/mgo.v2、dgrijalva/jwt-go)_第1张图片

你可能感兴趣的:(go,Go从不放弃到实战)