简单 web 服务与客户端开发实战

简单 web 服务与客户端开发实战

概述

利用 web 客户端调用远端服务是服务开发本实验的重要内容。其中,要点建立 API First 的开发理念,实现前后端分离,使得团队协作变得更有效率。

  • 任务目标
    • 选择合适的 API,实现从接口或资源(领域)建模,到 API 设计的过程
    • 使用 API 工具,编制 API 描述文件,编译生成服务器、客户端原型
    • 使用 Github 建立一个组织,通过 API 文档,实现 客户端项目 与 RESTful 服务项目同步开发
    • 使用 API 设计工具提供 Mock 服务,两个团队独立测试 API
    • 使用 travis 测试相关模块

项目地址

详细代码见我们的组织

功能简述

我们小组完成了一个简单的Web博客。
我们实现了以下功能:

  • 注册账户
  • 登陆账户
  • 查看博客
  • 查看评论
  • 发布评论

设计过程

Swagger生成接口文件

使用Swagger可以快速生成接口文件。只要输入yaml文件,点击上方 【Generate Client】则会生成一系列文件。
在这里插入图片描述
我们实现的接口如下:

GET /article/{id}/comments
GET /article/{id} 
GET /articles
POST /article/{id}/comments 
POST /user/login 
POST /user/register 

我在小组中负责后端有关于User的API设计
生成的后端文件结构如下:

在这里插入图片描述

  • db包存放数据库相关代码,数据库使用BoltDB
  • model包存放需要用到的数据结构
  • go包实现api代码

model

项目中需要声明几个数据结构,在model文件夹里的文件实现。

  • User
    User结构用于存储用户信息,包括用户名和密码。
package model

type User struct {
	Username string `json:"username,omitempty"`

	Password string `json:"password,omitempty"`
}

  • Comment
    Comment类存放评论信息,包括评论用户、文章id、评论信息、评论内容等
package model

type Comment struct {
	User string `json:"user,omitempty"`

	ArticleId int64 `json:"article_id,omitempty"`

	Date string `json:"date,omitempty"`

	Content string `json:"content,omitempty"`
}

API实现

response.go

response.go用来统一处理Response
MyResponse结构用来存储Response信息
Options单独处理OPTIONS(当需要loken认证时,会提前收到一个OPTIONS包)
Response用来发送回复信息

type MyResponse struct {
	OkMessage    interface{} `json:"ok,omitempty"`
	ErrorMessage interface{} `json:"error,omitempty"`
}

func Options(w http.ResponseWriter, r *http.Request){
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	w.Header().Set("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS")
	w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Origin, Access-Control-Allow-Credentials, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Authorization, X-Requested-With")
	w.Header().Set("Access-Control-Allow-Origin", "*")
	w.WriteHeader(http.StatusOK)
}

func Response(response interface{}, w http.ResponseWriter, code int) {
	jsonData, jErr := json.Marshal(&response)

	if jErr != nil {
		log.Fatal(jErr.Error())
	}

	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	w.Header().Set("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS")
	w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Origin, Access-Control-Allow-Credentials, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Authorization, X-Requested-With")
	w.Header().Set("Access-Control-Allow-Origin", "*")
	w.Write(jsonData)
	w.WriteHeader(code)
}

api_user.go

api_user.go实现User相关的API。
登录功能和注册功能大同小异,总体上都是读取body当中的参数,将其转为User结构体,再从数据库中获得用户名对应的User信息,然后对用户信息进行验证,例如验证用户是否存在,账户与密码是否对应等等,最后返回token。
以登录函数为例:

func UserLoginPost(w http.ResponseWriter, r *http.Request) {

	db.Init()

	var user model.User

	err := json.NewDecoder(r.Body).Decode(&user)

	if err != nil {
		Response(MyResponse{
			nil,
			"parameter error",
		}, w, http.StatusBadRequest)
		return
	}

	check := db.GetUser(user.Username)

	if check.Username != user.Username || check.Password != user.Password {
		Response(MyResponse{
			nil,
			"username or password error",
		}, w, http.StatusBadRequest)
		return
	}

发布评论需要对用户进行验证。首先验证用户token,然后读入body参数,并解码为Comment结构,再根据token更新User,获得文章id,然后获取当前时间,然后把评论加入到数据库中。

func ArticleIdCommentPost(w http.ResponseWriter, r *http.Request) {
	db.Init()
	token, isValid := ValidateToken(w, r)
	if isValid == false {
		Response(MyResponse{
			nil,
			"authentication fail",
		}, w, http.StatusBadRequest)
		return
	}

	var comment model.Comment

	err := json.NewDecoder(r.Body).Decode(&comment)
	if err != nil {
		Response(MyResponse{
			nil,
			err.Error(),
		}, w, http.StatusBadRequest)
		return
	}

	if v, ok := token.Claims.(jwt.MapClaims); ok {
		name, _ := v["name"].(string)
		comment.User = name
	}

	articleId := strings.Split(r.URL.Path, "/")[2]
	comment.ArticleId, err = strconv.ParseInt(articleId, 10, 64)
	if err != nil {
		Response(MyResponse{
			nil,
			err.Error(),
		}, w, http.StatusBadRequest)
		return
	}

	comment.Date = fmt.Sprintf("%d-%d-%d", time.Now().Year(), time.Now().Month(), time.Now().Day())

	articles := db.GetArticles(comment.ArticleId, 0)

	if len(articles) == 0 {
		Response(MyResponse{
			nil,
			"articles not found",
		}, w, http.StatusBadRequest)
		return
	}

	for i := 0; i < len(articles); i++ {
		articles[i].Comments = append(articles[i].Comments, comment)
	}

	err = db.PutArticles(articles)

	if err != nil {
		Response(MyResponse{
			nil,
			err.Error(),
		}, w, http.StatusBadRequest)
		return
	}

	Response(MyResponse{
		comment,
		nil,
	}, w, http.StatusOK)
}

Token

参考了相关网络,利用jwt实现了部分 API 支持 Token 认证
如添加评论需要对用户进行验证:

token, isValid := ValidateToken(w, r)

登录页需要验证:

tokenString, err := SignToken(user.Username)

具体的token实现代码如下:

func ValidateToken(w http.ResponseWriter, r *http.Request) (*jwt.Token, bool) {
	token, err := request.ParseFromRequest(r, request.AuthorizationHeaderExtractor,
		func(token *jwt.Token) (interface{}, error) {
			return []byte(SecretKey), nil
		})

	if err != nil {
		w.WriteHeader(http.StatusUnauthorized)
		fmt.Fprint(w, "Unauthorized access to this resource")
		return token, false
	}

	if !token.Valid {
		w.WriteHeader(http.StatusUnauthorized)
		fmt.Fprint(w, "token is invalid")
		return token, false
	}

	return token, true
}

func SignToken(userName string) (string, error) {

	token := jwt.New(jwt.SigningMethodHS256)
	claims := make(jwt.MapClaims)
	claims["exp"] = time.Now().Add(time.Hour * time.Duration(1)).Unix()
	claims["iat"] = time.Now().Unix()
	claims["name"] = userName
	token.Claims = claims
	return token.SignedString([]byte(SecretKey))
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值