我們經常會用csdn、部落格園、掘金、抖音、infoq、medium等寫文稿分享,我就以此寫個這樣的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
當出現以下介面時,表示成功!!!
此時也可以透過瀏覽器或postman進行測試
2.3 api介面測試
2.3.1 建立一篇文章
用postman測試api介面,注意資料格式是json
然後檢視後臺日誌
最後透過資料庫檢視錶裡是否已有資料
可以多新增幾條記錄方便下面測試
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,槓槓的。
參考:
PostgreSQL JSON Types www.postgresql.org/docs/9.4/dataty...
JSON Functions and Operators in PostgreSQL www.postgresql.org/docs/9.4/functi...
Using PostgreSQL JSONB with Go www.alexedwards.net/blog/using-pos...
Unmarshaling postgresql jsonb query response string into golang nested struct stackoverflow.com/questions/565588...
Golang Guide: A List of Top Golang Frameworks, IDEs & Tools medium.com/@quintinglvr/golang-gui...
Choosing a language for Freshdesk Microservices www.freshworks.com/company/java-vs...
7 Frameworks To Build A REST API In Go nordicapis.com/7-frameworks-to-bui...
Best practice for Database Open and Close connection forum.golangbridge.org/t/best-prac...
輕量級 Web 框架 Gin 結構分析 blog.itpub.net/31561269/viewspace-2...
-
How to build a REST API with Golang using Gin and Gorm
Building and Testing a REST API in Go with Gorilla Mux and PostgreSQL semaphoreci.com/community/tutorial...
Build a RESTful API with GO and PostgreSQL. scotch.io/@walugembe-peter/build-a...
Beautify your Golang project medium.com/m/global-identity?redir...
API Security Best Practices blog.bearer.sh/api-security-best-p...
後記:丟擲一個問題,既然golang也能快速開發微服務(前後端分離),那麼為啥還要用java呢,比如軟體大道、九龍湖的很多碼農,也不多想為什麼
本作品採用《CC 協議》,轉載必須註明作者和本文連結