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

查看对应的目录结构

Golang学习系列第五天: Golang和PostgreSQL开发 RESTful API_第1张图片

接着创建具体的实物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 

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

Golang学习系列第五天: Golang和PostgreSQL开发 RESTful API_第2张图片

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

Golang学习系列第五天: Golang和PostgreSQL开发 RESTful API_第3张图片

Golang学习系列第五天: Golang和PostgreSQL开发 RESTful API_第4张图片

 

2.3  api接口测试

2.3.1  创建一篇文章

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

Golang学习系列第五天: Golang和PostgreSQL开发 RESTful API_第5张图片

然后查看后台日志

Golang学习系列第五天: Golang和PostgreSQL开发 RESTful API_第6张图片

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

Golang学习系列第五天: Golang和PostgreSQL开发 RESTful API_第7张图片

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

Golang学习系列第五天: Golang和PostgreSQL开发 RESTful API_第8张图片

 

2.3.2  查询一篇文章

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

Golang学习系列第五天: Golang和PostgreSQL开发 RESTful API_第9张图片

2.3.3  查询所有文章

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

Golang学习系列第五天: Golang和PostgreSQL开发 RESTful API_第10张图片

2.3.4  修改一篇文章

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

文章id为1的原数据

Golang学习系列第五天: Golang和PostgreSQL开发 RESTful API_第11张图片

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

Golang学习系列第五天: Golang和PostgreSQL开发 RESTful API_第12张图片

再次查看id为1的文章

Golang学习系列第五天: Golang和PostgreSQL开发 RESTful API_第13张图片

2.3.5  删除一篇文章

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

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

Golang学习系列第五天: Golang和PostgreSQL开发 RESTful API_第14张图片

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

Golang学习系列第五天: Golang和PostgreSQL开发 RESTful API_第15张图片

最后执行删除

Golang学习系列第五天: Golang和PostgreSQL开发 RESTful API_第16张图片

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

Golang学习系列第五天: Golang和PostgreSQL开发 RESTful API_第17张图片

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

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

 

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

 

 

参考:

  1. PostgreSQL  JSON Types  https://www.postgresql.org/docs/9.4/datatype-json.html

  2. JSON Functions and Operators  in  PostgreSQL https://www.postgresql.org/docs/9.4/functions-json.html

  3. Using PostgreSQL JSONB with Go https://www.alexedwards.net/blog/using-postgresql-jsonb

  4. Unmarshaling postgresql jsonb query response string into golang nested struct https://stackoverflow.com/questions/56558802/unmarshaling-postgresql-jsonb-query-response-string-into-golang-nested-struct

  5. Golang Guide: A List of Top Golang Frameworks, IDEs & Tools https://medium.com/@quintinglvr/golang-guide-a-list-of-top-golang-frameworks-ides-tools-e7c7866e96c9

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

  7. 7 Frameworks To Build A REST API In Go https://nordicapis.com/7-frameworks-to-build-a-rest-api-in-go/

  8. Best practice for Database Open and Close connection https://forum.golangbridge.org/t/best-practice-for-database-open-and-close-connection/19021/2

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

  10. 使用 go 的 gin 和 gorm 框架来构建 RESTful API 微服务 https://learnku.com/go/t/24598

  11. How to build your first web application with Go https://freshman.tech/web-development-with-go/

  12. Building a Basic REST API in Go using Fiber https://tutorialedge.net/golang/basic-rest-api-go-fiber/

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

    https://blog.logrocket.com/how-to-build-a-rest-api-with-golang-using-gin-and-gorm/

  14. Building and Testing a REST API in Go with Gorilla Mux and PostgreSQL https://semaphoreci.com/community/tutorials/building-and-testing-a-rest-api-in-go-with-gorilla-mux-and-postgresql

  15. Build a RESTful API with GO and PostgreSQL. https://scotch.io/@walugembe-peter/build-a-restful-api-with-go-and-postgresql

  16. Beautify your Golang project https://medium.com/m/global-identity?redirectUrl=https%3A%2F%2Fitnext.io%2Fbeautify-your-golang-project-f795b4b453aa

  17. API Security Best Practices https://blog.bearer.sh/api-security-best-practices/

  18. Restful Web  API  (一切皆资源,设计接口必备书)Golang学习系列第五天: Golang和PostgreSQL开发 RESTful API_第18张图片

 

 

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

Golang学习系列第五天: Golang和PostgreSQL开发 RESTful API_第19张图片

 

 

你可能感兴趣的:(golang,postgresql,golang,postgresql)