Golang学习系列第五天: Golang和PostgreSQL开发 RESTful API

Golang学习系列第四天:操作数据库PostgreSQL,今天开始学习Golang和PostgreSQL开发 RESTful API接口,以Gorilla 为例。

1. 前言

我们经常会用csdn、博客园、掘金、抖音、infoq、medium等写文稿分享,我就以此写个这样的restful api接口,我写的是前后端分离式的接口(至于他们的是不是前后端一起或分离我就不知道了,也不想考究)。

一篇文章会有标题、子标题、摘要、背景图片、具体内容、文章分类、文章标签、文章作者(可能是联名写的,就像写书时多名作者)、发布时间、更新时间、文章类型(自己写的、转载的、或翻译老外的(比如掘金上就有不少))、是否置顶、是否被推荐、预计读的时间、浏览量、文章状态(拟稿、已发布、已逻辑删除)。

2. 构建文章 restful api 接口

注:我只写基本的文章api接口

2.1 建库建表

进入到数据库环境中,创建数据库csdn,然后创建文章表结构,我没有使用那么多字段列。

[root@master ~]# psql -h 192.168.8.200 -p 5432 -U postgres
psql (12.3)
Type "help" for help.

postgres=# CREATE DATABASE csdn;
CREATE DATABASE
postgres=# \c csdn
You are now connected to database "csdn" as user "postgres".
csdn=# CREATE TABLE article (
csdn(#     id SERIAL PRIMARY KEY,
csdn(#     title   char(100),
csdn(#     content text,
csdn(#     category  text ARRAY[4],
csdn(#     tag  text ARRAY[4],
csdn(#     type  char(10),
csdn(#     author  text ARRAY[10]
csdn(# );
CREATE TABLE
csdn=# INSERT INTO article VALUES (1,
csdn(# 'Golang学习系列第五天: Golang和PostgreSQL开发 RESTful API',
csdn(# '用go和postgresql开发restful api接口的过程',
csdn(# ARRAY['golang','postgresql'],
csdn(# ARRAY['golang', 'go','postgresql'],
csdn(# '原创',
csdn(# ARRAY['dongguangming', 'dgm']
csdn(# );
INSERT 0 1
csdn=# select * from article;
 id |                                                      title                                                      |                  conte
nt                  |      category       |          tag           |     type     |       author        
----+-----------------------------------------------------------------------------------------------------------------+-----------------------
--------------------+---------------------+------------------------+--------------+---------------------
  1 | Golang学习系列第五天: Golang和PostgreSQL开发 RESTful API                                                       | 用go和postgresql开发re
stful api接口的过程 | {golang,postgresql} | {golang,go,postgresql} | 原创         | {dongguangming,dgm}
(1 row)

csdn=# 

2. 2 开始golang开发接口

2.2.1 新建工程csdn

建工程目录csdn,并创建相应的子文件夹(类似于mvc那套结构)

[root@master ~]# cd /dongguangming/goworkspace/
[root@master goworkspace]# pwd
/dongguangming/goworkspace
[root@master goworkspace]# mkdir csdn csdn/{models,middlewares,responses,api}
[root@master goworkspace]# cd csdn/

然后初始化自定义模块

go mod init csdn

查看对应的目录结构

接着创建具体的实物model,http响应数据格式,数据库连接

2.2.2 新建文章实体

先建article.go文件

[root@master csdn]# touch models/article.go

,键入以下代码

package models

import (
    "errors"
    "github.com/jinzhu/gorm"
    "strings"
)

type Article struct {
    gorm.Model
    Title        string `gorm:"size:100;not null;unique" json:"title"`
    Content      string `gorm:"not null"                 json:"content"`
    Category     string `gorm:"size:50;not null"         json:"category"`
    Tag          string `gorm:"size:50;not null"         json:"tag"`
  Author       string `gorm:"size:50;not null"         json:"author"`
}

func (article *Article) Prepare() {
    article.Title = strings.TrimSpace(article.Title)
    article.Content = strings.TrimSpace(article.Content)
    article.Category = strings.TrimSpace(article.Category)
    article.Tag = strings.TrimSpace(article.Tag)
    article.Author = strings.TrimSpace(article.Author)
}

func (article *Article) Validate() error {
    if article.Title == "" {
        return errors.New("Name is required")
    }
    if article.Content == "" {
        return errors.New("Description about venue is required")
    }

    if article.Category == "" {
        return errors.New("Category of venue is required")
    }
    if article.Tag == "" {
        return errors.New("Category of venue is required")
    }
    if article.Author == "" {
        return errors.New("Category of venue is required")
    }
    return nil
}

func (article *Article) SaveArticle(db *gorm.DB) (*Article, error) {
    var err error

    // Debug a single operation, show detailed log for this operation
    err = db.Debug().Create(&article).Error
    if err != nil {
        return &Article{}, err
    }
    return article, nil
}

func (article *Article) GetArticle(db *gorm.DB) (*Article, error) {
    newArticle := &Article{}
    if err := db.Debug().Table("article").Where("title = ?", article.Title).First(newArticle).Error; err != nil {
        return nil, err
    }
    return newArticle, nil
}

func GetArticles(db *gorm.DB) (*[]Article, error) {
    articles := []Article{}
    if err := db.Debug().Table("article").Find(&articles).Error; err != nil {
        return &[]Article{}, err
    }
    return &articles, nil
}

func GetArticleById(id int, db *gorm.DB) (*Article, error) {
    article := &Article{}
    if err := db.Debug().Table("article").Where("id = ?", id).First(article).Error; err != nil {
        return nil, err
    }
    return article, nil
}

func (article *Article) UpdateArticle(id int, db *gorm.DB) (*Article, error) {
    if err := db.Debug().Table("article").Where("id = ?", id).Updates(Article{
        Title : article.Title,
      Content : article.Content,
      Category : article.Category,
      Tag : article.Tag,
      Author : article.Author}).Error; err != nil {
        return &Article{}, err
    }
    return article, nil
}

func DeleteArticle(id int, db *gorm.DB) error {
    if err := db.Debug().Table("article").Where("id = ?", id).Delete(&Article{}).Error; err != nil {
        return err
    }
    return nil
}

2.2.3 建立json响应数据格式

继续在csdn目录下,创建响应格式文件json.go,json很流行了,以前的xml数据交换格式被干掉了

[root@master csdn]# touch responses/json.go

然后键入以下代码

package responses

import (
    "encoding/json"
    "fmt"
    "net/http"
)

// JSON returns a well formated response with a status code
func JSON(w http.ResponseWriter, statusCode int, data interface{}) {
    w.WriteHeader(statusCode)
    err := json.NewEncoder(w).Encode(data)
    if err != nil {
        fmt.Fprintf(w, "%s", err.Error())
    }
}

// ERROR returns a jsonified error response along with a status code.
func ERROR(w http.ResponseWriter, statusCode int, err error) {
    if err != nil {
        JSON(w, statusCode, struct {
            Error string `json:"error"`
        }{
            Error: err.Error(),
        })
        return
    }
    JSON(w, http.StatusBadRequest, nil)
}

2.2.4 建立公共中间件

在命令行输入命令建立公共文件

[root@master csdn]# touch middlewares/middlewares.go

键入以下代码

package middlewares

import (
    "context"
    "net/http"
    "os"
    "strings"

    jwt "github.com/dgrijalva/jwt-go"

    "csdn/responses"
)

// SetContentTypeMiddleware sets content-type to json
func SetContentTypeMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        next.ServeHTTP(w, r)
    })
}

2.2.5 创建具体的api接口

先创建公共基础性代码文件,比如数据库连接、路由表和将来的token验证等,在此仅数据库连接为例

创建base.go文件和具体的文章接口文件article.go

[root@master csdn]# touch api/base.go  api/article.go 

然后分别编辑两文件,base.go文件键入以下代码

package api

import (
    "fmt"
    "log"
    "net/http"

    "github.com/gorilla/mux"
    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/postgres" //postgres

    "csdn/middlewares"
    "csdn/models"
    "csdn/responses"
)

type App struct {
    Router *mux.Router
    DB     *gorm.DB
}

// 初始化数据库连接,添加路由表
func (a *App) Initialize(DbHost, DbPort, DbUser, DbName, DbPassword string) {
    var err error
    DBURI := fmt.Sprintf("host=%s port=%s user=%s dbname=%s sslmode=disable password=%s", DbHost, DbPort, DbUser, DbName, DbPassword)

    a.DB, err = gorm.Open("postgres", DBURI)
    if err != nil {
        fmt.Printf("\n 不能连接到数据库: %s", DbName)
        log.Fatal("发生了连接数据库错误:", err)
    } else {
        fmt.Printf("恭喜连接到数据库: %s", DbName)
    }

    a.DB.Debug().AutoMigrate(&models.Article{}) //database migration

    a.Router = mux.NewRouter().StrictSlash(true)
    a.initializeRoutes()
}

func (a *App) initializeRoutes() {
    a.Router.Use(middlewares.SetContentTypeMiddleware) // setting content-type to json

    a.Router.HandleFunc("/", home).Methods("GET")

    s := a.Router.PathPrefix("/api").Subrouter() // subrouter to add auth middleware

    s.HandleFunc("/article", a.CreateArticle).Methods("POST")
    s.HandleFunc("/article", a.GetArticles).Methods("GET")
    s.HandleFunc("/article/{id:[0-9]+}", a.GetArticle).Methods("GET")
    s.HandleFunc("/article/{id:[0-9]+}", a.UpdateArticle).Methods("PUT")
    s.HandleFunc("/article/{id:[0-9]+}", a.DeleteArticle).Methods("DELETE")
}

func (a *App) RunServer() {
    log.Printf("\nServer starting on port 9999")
    log.Fatal(http.ListenAndServe("192.168.8.200:9999", a.Router))
}

func home(w http.ResponseWriter, r *http.Request) {
    responses.JSON(w, http.StatusOK, "Golang学习系列第五天: Golang和PostgreSQL开发 RESTful API")
}

然后article.go文件键入以下代码

package api

import (
    "encoding/json"
    "io/ioutil"
    "net/http"
    "strconv"

    "github.com/gorilla/mux"

    "csdn/models"
    "csdn/responses"
)

// 创建文章
func (a *App) CreateArticle(w http.ResponseWriter, r *http.Request) {
    var resp = map[string]interface{}{"status": "success", "message": "文章创建成功"}

    article := &models.Article{}
    body, err := ioutil.ReadAll(r.Body)
    if err != nil {
        responses.ERROR(w, http.StatusBadRequest, err)
        return
    }

    err = json.Unmarshal(body, &article)
    if err != nil {
        responses.ERROR(w, http.StatusBadRequest, err)
        return
    }

    article.Prepare()

    if err = article.Validate(); err != nil {
        responses.ERROR(w, http.StatusBadRequest, err)
        return
    }

    if existed, _ := article.GetArticle(a.DB); existed != nil {
        resp["status"] = "failed"
        resp["message"] = "文章已存在"
        responses.JSON(w, http.StatusBadRequest, resp)
        return
    }

    articleCreated, err := article.SaveArticle(a.DB)
    if err != nil {
        responses.ERROR(w, http.StatusBadRequest, err)
        return
    }

    resp["article"] = articleCreated
    responses.JSON(w, http.StatusCreated, resp)
    return
}

//获取所有文章
func (a *App) GetArticles(w http.ResponseWriter, r *http.Request) {
    articles, err := models.GetArticles(a.DB)
    if err != nil {
        responses.ERROR(w, http.StatusInternalServerError, err)
        return
    }
    responses.JSON(w, http.StatusOK, articles)
    return
}

//获取单篇文章
func (a *App) GetArticle(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    id, _ := strconv.Atoi(vars["id"])

    article, err := models.GetArticleById(id, a.DB)
    if err != nil {
        responses.ERROR(w, http.StatusInternalServerError, err)
        return
    }
    responses.JSON(w, http.StatusOK, article)
    return
}

//更改文章
func (a *App) UpdateArticle(w http.ResponseWriter, r *http.Request) {
    var resp = map[string]interface{}{"status": "success", "message": "修改文章成功!!!"}

    vars := mux.Vars(r)

    id, _ := strconv.Atoi(vars["id"])

    //article, err := models.GetArticleById(id, a.DB)

    body, err := ioutil.ReadAll(r.Body)
    if err != nil {
        responses.ERROR(w, http.StatusBadRequest, err)
        return
    }

    articleUpdate := models.Article{}
    if err = json.Unmarshal(body, &articleUpdate); err != nil {
        responses.ERROR(w, http.StatusBadRequest, err)
        return
    }

    articleUpdate.Prepare()

    _, err = articleUpdate.UpdateArticle(id, a.DB)
    if err != nil {
        responses.ERROR(w, http.StatusInternalServerError, err)
        return
    }

    responses.JSON(w, http.StatusOK, resp)
    return
}

//删除文章
func (a *App) DeleteArticle(w http.ResponseWriter, r *http.Request) {
    var resp = map[string]interface{}{"status": "success", "message": "文章删除成功!!!"}

    vars := mux.Vars(r)

    id, _ := strconv.Atoi(vars["id"])

    //article, err := models.GetArticleById(id, a.DB)

    err := models.DeleteArticle(id, a.DB)
    if err != nil {
        responses.ERROR(w, http.StatusInternalServerError, err)
        return
    }
    responses.JSON(w, http.StatusOK, resp)
    return
}

2.2.6 创建主文件main.go

最后创建主文件,类似于java里启动文件

[root@master csdn]# touch main.go

键入以下代码

package main

import (
    "log"
    "os"

    "github.com/joho/godotenv"

    "csdn/api"
)

func main() {
    if err := godotenv.Load(); err != nil {
        log.Fatal("不能加载属性.env文件")
    }

    app := api.App{}
    app.Initialize(
        os.Getenv("DB_HOST"),
        os.Getenv("DB_PORT"),
        os.Getenv("DB_USER"),
        os.Getenv("DB_NAME"),
        os.Getenv("DB_PASSWORD"))

    app.RunServer()
}

创建数据库配置文件.env,

[root@master csdn]# touch  .env 

键入数据库具体连接信息

DB_HOST=192.168.8.200
DB_USER=postgres
DB_PASSWORD=12345678
DB_NAME=csdn
DB_PORT=5432

终于敲完了代码,运行主程序

[root@master csdn]# go  run main.go 

当出现以下界面时,表示成功!!!

go启动

此时也可以通过浏览器或postman进行测试

2.3 api接口测试

2.3.1 创建一篇文章

用postman测试api接口,注意数据格式是json

api创建文章接口

然后查看后台日志

文章创建日志

最后通过数据库查看表里是否已有数据

可以多添加几条记录方便下面测试

2.3.2 查询一篇文章

继续用postman测试api接口,注意数据格式是json,注意是根据id查询一篇文章

2.3.3 查询所有文章

继续用postman测试api接口,注意数据格式是json

​" class="reference-link">

2.3.4 修改一篇文章

继续用postman测试api接口,注意数据格式是json

文章id为1的原数据

修改id为1的数据,这里修改内容和作者举例

修改文章

再次查看id为1的文章

2.3.5 删除一篇文章

继续用postman测试api接口,注意数据格式是json,要删除的文章id为6

预先添加一条要删除的数据记录

然后执行删除操作,删除前先查一次id为6的文章,看是否有记录

最后执行删除

再次查询文章id为6的记录就没有了

至此,一个基于Golang和PostgreSQL开发的restful api接口就结束了!!!接口,一切皆资源api接口。

后续可以继续完善(比如token、缓存、mq、搜索等),和ava版本可以pk

再次推荐下接口测试工具Postman,杠杠的。

参考:

  1. PostgreSQL JSON Types www.postgresql.org/docs/9.4/dataty...

  2. JSON Functions and Operators in PostgreSQL www.postgresql.org/docs/9.4/functi...

  3. Using PostgreSQL JSONB with Go www.alexedwards.net/blog/using-pos...

  4. Unmarshaling postgresql jsonb query response string into golang nested struct stackoverflow.com/questions/565588...

  5. Golang Guide: A List of Top Golang Frameworks, IDEs & Tools medium.com/@quintinglvr/golang-gui...

  6. Choosing a language for Freshdesk Microservices www.freshworks.com/company/java-vs...

  7. 7 Frameworks To Build A REST API In Go nordicapis.com/7-frameworks-to-bui...

  8. Best practice for Database Open and Close connection forum.golangbridge.org/t/best-prac...

  9. 轻量级 Web 框架 Gin 结构分析 blog.itpub.net/31561269/viewspace-2...

  10. How to build your first web application with Go

  11. How to build a REST API with Golang using Gin and Gorm

    blog.logrocket.com/how-to-build-a-...

  12. Building and Testing a REST API in Go with Gorilla Mux and PostgreSQL semaphoreci.com/community/tutorial...

  13. Build a RESTful API with GO and PostgreSQL. scotch.io/@walugembe-peter/build-a...

  14. Beautify your Golang project medium.com/m/global-identity?redir...

  15. API Security Best Practices blog.bearer.sh/api-security-best-p...

  16. Restful Web API (一切皆资源,设计接口必备书)restful api

后记:抛出一个问题,既然golang也能快速开发微服务(前后端分离),那么为啥还要用java呢,比如软件大道、九龙湖的很多码农,也不多想为什么
golang开发微服务

本作品采用《CC 协议》,转载必须注明作者和本文链接
人生,不应设限
dongguangming
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!