我们经常会用csdn、博客园、掘金、抖音、infoq、medium等写文稿分享,我就以此写个这样的restful api接口,我写的是前后端分离式的接口(至于他们的是不是前后端一起或分离我就不知道了,也不想考究)。
一篇文章会有标题、子标题、摘要、背景图片、具体内容、文章分类、文章标签、文章作者(可能是联名写的,就像写书时多名作者)、发布时间、更新时间、文章类型(自己写的、转载的、或翻译老外的(比如掘金上就有不少))、是否置顶、是否被推荐、预计读的时间、浏览量、文章状态(拟稿、已发布、已逻辑删除)。
注:我只写基本的文章api接口
进入到数据库环境中,创建数据库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=#
建工程目录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响应数据格式,数据库连接
先建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
}
继续在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)
}
在命令行输入命令建立公共文件
[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)
})
}
创建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
}
最后创建主文件,类似于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进行测试
用postman测试api接口,注意数据格式是json
然后查看后台日志
最后通过数据库查看表里是否已有数据
继续用postman测试api接口,注意数据格式是json,注意是根据id查询一篇文章
继续用postman测试api接口,注意数据格式是json
继续用postman测试api接口,注意数据格式是json
文章id为1的原数据
修改id为1的数据,这里修改内容和作者举例
再次查看id为1的文章
继续用postman测试api接口,注意数据格式是json,要删除的文章id为6
预先添加一条要删除的数据记录
然后执行删除操作,删除前先查一次id为6的文章,看是否有记录
最后执行删除
再次查询文章id为6的记录就没有了
至此,一个基于Golang和PostgreSQL开发的restful api接口就结束了!!!接口,一切皆资源api接口。
后续可以继续完善(比如token、缓存、mq、搜索等),和ava版本可以pk
再次推荐下接口测试工具Postman,杠杠的。
PostgreSQL JSON Types https://www.postgresql.org/docs/9.4/datatype-json.html
JSON Functions and Operators in PostgreSQL https://www.postgresql.org/docs/9.4/functions-json.html
Using PostgreSQL JSONB with Go https://www.alexedwards.net/blog/using-postgresql-jsonb
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
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
Choosing a language for Freshdesk Microservices https://www.freshworks.com/company/java-vs-golang-blog/
7 Frameworks To Build A REST API In Go https://nordicapis.com/7-frameworks-to-build-a-rest-api-in-go/
Best practice for Database Open and Close connection https://forum.golangbridge.org/t/best-practice-for-database-open-and-close-connection/19021/2
轻量级 Web 框架 Gin 结构分析 http://blog.itpub.net/31561269/viewspace-2637490/
使用 go 的 gin 和 gorm 框架来构建 RESTful API 微服务 https://learnku.com/go/t/24598
How to build your first web application with Go https://freshman.tech/web-development-with-go/
Building a Basic REST API in Go using Fiber https://tutorialedge.net/golang/basic-rest-api-go-fiber/
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/
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
Build a RESTful API with GO and PostgreSQL. https://scotch.io/@walugembe-peter/build-a-restful-api-with-go-and-postgresql