极简博客网站开发

本次项目的重点是利用web客户端调用远端服务,并且实现前后端分离开发,以此提高团队协作的效率。
我们的大致开发流程是:

  1. 设计REST风格的API;
  2. 使用swagger生成后端框架,并完成后端的编写;与此同时,使用vue框架完成前端的编写;
  3. 使用postman测试后端,使用mock测试前端;
  4. 运行测试
  5. 完成文档的编写

下面是具体的实现过程。


一. 构思阶段

项目的要求是,至少含有6个API,4种资源。我们设计的博客网站,支持以下六个API:

  1. /openapi101/auth/signup --用户注册
  2. /openapi101/auth/signin --用户登陆
  3. /openapi101/article --发布文章
  4. /openapi101/article/articleID --根据ID查找文章
  5. /openapi101/article/articleID/comment --查看文章评论
  6. /openapi101/article/articleID/comment --发表评论

四种资源分别是:

  1. 用户(包含数据:用户名,密码)
  2. 文章(包含数据:文章ID,标题,内容)
  3. 评论(包含数据:用户名,评论内容)
  4. 标签(包含数据:文章标签)

二. API设计

使用swagger-editor来编写API文档,遵循yaml语法:

swagger: "2.0"
info:
  description: "A simple API to learn how to write OpenAPI Specification"
  version: "1.0.0"
  title: "Simple API"
host: "simple.api"
basePath: "/openapi101"
schemes:
- "https"
paths:
  /auth/signin:
    post:
      summary: "sign in"
      parameters:
      - name: "username"
        in: "path"
        required: true
        type: "string"
        x-exportParamName: "Username"
      responses:
        "200":
          description: "A list of Person"
          schema:
            required:
            - "username"
            properties:
              username:
                type: "string"
        "404":
          description: "The user does not exists"
  /auth/signup:
    post:
      summary: "sign up a user"
      description: "sign up a user"
      parameters:
      - in: "body"
        name: "user"
        required: false
        schema:
          $ref: "#/definitions/user"
        x-exportParamName: "User"
      responses:
        "204":
          description: "OK"
        "400":
          description: "Wrong"
  /article/{article_id}:
    get:
      summary: "get post"
      parameters:
      - name: "article_id"
        in: "path"
        description: "article id"
        required: true
        type: "string"
        x-exportParamName: "ArticleId"
      responses:
        "200":
          description: "A post"
          schema:
            properties:
              name:
                type: "string"
              data:
                type: "string"
  /article:
    post:
      summary: "Create article"
      parameters:
      - in: "body"
        name: "content"
        required: true
        schema:
          $ref: "#/definitions/content"
        x-exportParamName: "Content"
      responses:
        "204":
          description: "A post"
definitions:
  user:
    required:
    - "password"
    - "username"
    properties:
      username:
        type: "string"
      password:
        type: "string"
  content:
    required:
    - "article_content"
    properties:
      article_content:
        type: "string"
      author:
        type: "string"

点击go-server后,swagger会自动生成后端框架,结构如下:

- go-server
	- .swagger-codegen
		- VERSION
	- api
		- swagger.yaml
	- go
		- routers.go
		- logger.go
		- user.go
		- user_api.go
		- ...
		- README.md
	- main.go

其中,routers.go定义了url的路由去向,不同的url对应不同的API实现。
routers.go如下:

package swagger

import (
	"fmt"
	"net/http"
	"strings"

	"github.com/gorilla/mux"
)

type Route struct {
	Name        string
	Method      string
	Pattern     string
	HandlerFunc http.HandlerFunc
}

type Routes []Route

func NewRouter() *mux.Router {
	router := mux.NewRouter().StrictSlash(true)
	for _, route := range routes {
		var handler http.Handler
		handler = route.HandlerFunc
		handler = Logger(handler, route.Name)

		router.
			Methods(route.Method).
			Path(route.Pattern).
			Name(route.Name).
			Handler(handler)
	}

	return router
}

func Index(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello World!")
}

var routes = Routes{
	Route{
		"Index",
		"GET",
		"/openapi101/",
		Index,
	},

	Route{
		"ArticleArticleIdGet",
		strings.ToUpper("Get"),
		"/openapi101/article/{article_id}",
		ArticleArticleIdGet,
	},

	Route{
		"ArticlePost",
		strings.ToUpper("Post"),
		"/openapi101/article",
		ArticlePost,
	},

	Route{
		"AuthSigninPost",
		strings.ToUpper("Post"),
		"/openapi101/auth/signin",
		AuthSigninPost,
	},

	Route{
		"AuthSignupPost",
		strings.ToUpper("Post"),
		"/openapi101/auth/signup",
		AuthSignupPost,
	},
}

三.API实现

后端的编写中,我主要负责两个API的实现:文章的发布和根据ID查找文章。
首先,定义Article结构体:

type Article struct {

	Id int `json:"id"`

	ArticleContent string `json:"articlecontent"`

	Author string `json:"author"`

	Title string `json:"title"`

}

在介绍发布文章之前,我们需要明确一点:如何确保用户在发布文章的时候已经是登陆状态呢?又如何确保用户不能以别人的名义发布文章呢?
这里使用的是jwt(JOSON Web Token)来认证,用户注册之后, 服务器生成一个 JWT token返回给浏览器, 浏览器向服务器请求数据时将 JWT token 发给服务器, 服务器用 signature 中定义的方式解码 JWT 获取用户信息。客户端获得Token后,可以将其储存在cookie中,当要请求需要授权的服务时,把Token赋给Header的Authorization项。
认证函数如下:

func Authorization(w http.ResponseWriter, r *http.Request, username string) bool {
	token, _ := request.ParseFromRequest(r, 
		request.AuthorizationHeaderExtractor,
        func(token *jwt.Token) (interface{}, error) {
            return []byte(SecretKey), nil
        })
	claim, _ := token.Claims.(jwt.MapClaims)
  
    if username != claim["sub"] {
    	return false
    }
	return true
}

接下来,处理发布文章的请求和查找文章的请求,分别是POST和GET操作。文章存储在数据库中(采用的是boltDB),两种请求分别对应数据库的写和读操作。
发布文章:

func ArticlePost(w http.ResponseWriter, r *http.Request) {
	db, err := bolt.Open("my.db", 0600, nil)
    if err != nil {
        log.Fatal(err)
    }
	defer db.Close()

	fmt.Print(r.Header)
	fmt.Print(r.Body)

	var article Article
	err = json.NewDecoder(r.Body).Decode(&article)

	fmt.Print(article)

	ok := Authorization(w, r, article.Author)
	if !ok {
		response := ErrorResponse{"Authorization failed!"}
		JsonResponse(response, w, http.StatusUnauthorized)
		return
	}

	if article.Title == "" || article.ArticleContent == "" {
		response := ErrorResponse{"Wrong title or content"}
		JsonResponse(response, w, http.StatusBadRequest)
		return
	}

	if err != nil {
		response := ErrorResponse{err.Error()}
		JsonResponse(response, w, http.StatusBadRequest)
		return
	}

	err = db.Update(func(tx *bolt.Tx) error {
		b, err := tx.CreateBucketIfNotExists([]byte("Article"))
		if err != nil {
			return err
		}
		id, _ := b.NextSequence()
		article.Id = int(id)
		fmt.Println("%d",int(id))
		encoded, err := json.Marshal(article)
		byte_id := itob(article.Id)
		return b.Put(byte_id, encoded)
	})

	if err != nil {
		response := ErrorResponse{err.Error()}
		JsonResponse(response, w, http.StatusBadRequest)
		return
	}

	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	w.WriteHeader(http.StatusOK)

}

根据ID查找文章:

func ArticleArticleIdGet(w http.ResponseWriter, r *http.Request) {
	db, err := bolt.Open("my.db", 0600, nil)
    if err != nil {
        log.Fatal(err)
    }
	defer db.Close()

	articleId := strings.Split(r.URL.Path, "/")[3]
	Id, err:= strconv.Atoi(articleId)
	if err != nil {
		reponse := ErrorResponse{"Wrong ArticleId"}
		JsonResponse(reponse, w, http.StatusBadRequest)
		return
	}
	var article Article
	err = db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("Article"))
		if b != nil {
			v := b.Get(itob(Id))
			if v == nil {
				return errors.New("Article Not Exists")
			} else {
				_ = json.Unmarshal(v, &article)
				return nil
			}
		} else {
			return errors.New("Article Not Exists")
		}
	})

	if err != nil {
		reponse := ErrorResponse{err.Error()}
		JsonResponse(reponse, w, http.StatusNotFound)
		return
	}

	JsonResponse(article, w, http.StatusOK)

}

四.使用postman进行测试

由于前端开发尚未完成,这里需要借助postman发起请求。开启服务器后,在postman上向服务器发起POST请求发布文章:
在这里插入图片描述
发布成功时,显示状态码200。为了验证文章是否正确存至数据库,再在postman上向服务器发起GET请求,根据ID查找文章:
在这里插入图片描述
查找成功,显示状态码200,结果正好是刚才发布的文章。说明发布文章、根据ID查找文章这两个API的实现基本正确。


五.前端开发

前端开发采用VUE框架,这里不再赘述。
最后,放上github项目地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值