go从0到1项目实战体系二十二:gin构建一个http server

1. 构建一个http server:

// api.test.com/topic/main.go:
type Topic struct {
	Id int           // 如果写成小写的,不能访问,因为是私有的.
	Title string
}
func main() {
	data := make(map[string]interface{})
	data["name"] = "david"
	data["age"] = 12
	// 默认路由
	router := gin.Default()
	router.GET("/", func(context *gin.Context) {
		context.Writer.Write([]byte("hello"))                                 // (1)
		// context.JSON(http.StatusOK, data)                                  // (2)
		// context.JSON(http.StatusOK, gin.H{"name": "daivd", "age": 12})     // (3)
		// context.JSON(http.StatusOK, Topic{100, "话题标题"})                 // (4)
	})
	router.Run()
}

(1). 返回text/plain格式的结果:hello

HTTP/1.1 200 OK
Date: Fri, 01 Nov 2019 04:09:18 GMT
Content-Length: 5
Content-Type: text/plain; charset=utf-8

(2). 返回json的格式:{“age”:12, “name”:“david”}

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Mon, 04 Nov 2019 11:00:06 GMT
Content-Length: 25

(3). 返回如上json的格式:{“age”:12, “name”:“david”}

# 1. gin.H{}底层实现方案:
// H is a shortcut for map[string]interface{}
type H map[string]interface{}

# 2. 模拟实现:
type M map[string]interface{}. make的形式:
    m := make(M)
    m["name"] = "david"
    # 在使用map,需要先make,make的作用就是给map分配数据空间

②. 直接赋值:
    map[string]interface{}{"name": "daivd"}. M就相当于map[string]interface{}的简写:
    M{"name": "daivd"}

(4). 对象返回如上json的格式:{“Id”:100, “Title”:“话题标题”}


2. API的URL规则设计:

2.1 httprouter库:

①. gin的路由使用的是httprouter库.
②. 不支持正则、不支持固定路径和参数路径.
③. 保证了性能,但功能不强大.

2.2 API设计建议:

(1). 常规上的路由:

GET /topic/{topic_id}:获取帖子明细
GET /topic/{user_name}:获取用户发布的帖子列表
GET /topic/top:获取最热帖子列表

(2). 建议改进路由规则:

①. 最好API前面加上版本:
   GET /v1/topic/:id

②. 尽可能使用复数的语义化名词:
   GET /v1/topics

③. 使用GET参数规划数据展现规则:
   GET /v1/users                  // 显示全部用户
   GET /v1/users?limit=10         // 显示10条用户
   GET /v1/topics?username=xx     // 显示xx的帖子

3.3 增加其它路由:

api.test.com/topic/main.go:

router := gin.Default()
router.GET("/v1/topics/:topic_id", func(context *gin.Context) {
	context.String(http.StatusOK, "获取%s的帖子", context.Param("topic_id"))
})
router.GET("/v1/topics", func(context *gin.Context) {
	if context.Query("username") == "" {
		context.String(http.StatusOK, "获取所有帖子")
	} else {
		context.String(http.StatusOK, "获取%s所有帖子", context.Query("username"))
	}
})
router.Run()

注:

①. 以下几条路由不允许:
   router.GET("/topic/:id")
   router.GET("/topic/topic")
   router.GET("/topic/topic/:username")

②. 报错:
   panic: 'topic' in new path '/topic/topic' conflicts with existing wildcard ':topic_id' in existing prefix '/:topic_id'

③. context.Param可以获取"/v1/topics/:topic_id"中以冒号的形式设置url参数topic_id.

④. context.Query可以当get传参的时候,url上显示的参数.

3.4 路由分组:

(1). api.test.com/topic/main.go:

import "topic.test.com/src"  // moduel/文件名
func main() {
	router := gin.Default()
	// 定义一个路由分组
	v1 := router.Group("/v1/topics")
	// {}不是强关联,只是为了美观,做代码切割,不写也可以,有作用域的区别
	{
		// 什么都不写,表示与组的路由是一致的(GET /v1/topics)
		v1.GET("", src.GetList)  // 将业务代码分离出去
		v1.GET("/:topic_id", func(context *gin.Context) {
			context.String(http.StatusOK, "获取%s的帖子", context.Param("topic_id"))
		})
		// 在这个下面所有定义的路由都需要鉴权
		v1.Use(src.MustLogin())
		{
			v1.POST("", src.AddTopic)
		}
	}
	router.Run()
}:. 如果src.GetList(),不想写src的话.需要这样引入  =>  . "topic.test.com/src". 不能写src.GetList(),否则是表示执行函数得到的结果.. 如果想写src.GetList(),则需要改造如下:
   func GetList2() gin.HandlerFunc {
	  return func (c *gin.Context) {
		  //
      }
   }. v1.Use的参数为HandlerFunc,所以是执行函数返回结果.. 为了美观,可以在路由上加一个{}代码块(代码块有作用域),不是强关联,只是为了美观.

(2). api.test.com/topic/src/TopicDao.go:

src是所有业务代码

func MustLogin() gin.HandlerFunc {
	return func (c *gin.Context) {
		if _, status := c.GetQuery("token");!status {
			c.String(http.StatusUnauthorized, "授权失败")
			c.Abort()  // 没有的话,会继续往下执行
		} else {
			c.Next()
		}
	}
}
func GetList(c *gin.Context) {
	// if c.Query("username") == "" {
	//  	c.String(http.StatusOK, "获取所有帖子")
	// } else {
	//  	c.String(http.StatusOK, "获取%s所有帖子", c.Query("username"))
	// }
	// get参数校验
	query := TopicQuery{}
	err := c.BindQuery(&query)  // 是BindQuery
	if err != nil {
		c.JSON(500, "参数错误")  // 自动报一个400 Bad Request的错误,写成500也是400
	} else {
		c.JSON(http.StatusOK, query)
	}
}
func AddTopic(c *gin.Context) {
	// c.JSON(http.StatusOK, CreateTopic(1, "PHP"))
	// post参数校验
	query := Topic{}
	err := c.BindJSON(&query)  // 是BindJSON
	if err != nil {
		c.JSON(500, "参数错误")
	} else {
		c.JSON(http.StatusOK, query)
	}
}:. Dao层操作数据库实体、redis实体.. 路由测试:
   POST localhost:8080/v1/topics  => 结果是:授权失败
   POST localhost:8080/v1/topics?token=123  => 结果是:{ "id": 1, "title": "PHP" }
   如果没有json映射字段则返回:{ "TopicId": 1, "TopicTitle": "PHP" }. 参数绑定Model:
   a. GetList中,如果参数有username、page、pagesize等,if来判断的话,非常繁琐.
   b. 可以将get参数绑定(get参数映射为一个模型).
   c. 很多流行框架支持参数绑定Model(传过来的参数与model进行绑定).. GET localhost:8080/v1/topics?username=david&page=1&pagesize=10
   => 如果TopicQuery struct中没有form字段会是{ "username": "", "page": 0, "pagesize": 0 }
   localhost:8080/v1/topics?username=david&page=1
   => { "username": "david", "page": 1, "pagesize": 0 }
   localhost:8080/v1/topics?username=david&page=1&pagesize=10
   => { "username": "david", "page": 1, "pagesize": 10 }
   localhost:8080/v1/topics?username=david&pagesize=10
   => 参数错误
   localhost:8080/v1/topics?username=david&page=0
   => 参数错误,page为0
   localhost:8080/v1/topics?username=david&page=""
   => 参数错误,page为""

(3). api.test.com/topic/src/TopicModel.go:

package src
type Topic struct {
	TopicId int `json:"id"`  // json反射的映射字段
	TopicTitle string `json:"title" binding:"min=4,max=20"`  // 在4和20字之间,中英文都是一样
	TopicShortTitle string `json:"stitle" binding:"requried,nefield=TopicTitle"`  // 不能与TopicTitle一样
	UserIP string `json:"ip" binding:"ipv4"`
	TopicScore int `json:"score" binding:"omitempty,gt=5"`  // 可以不填,填了就大于5
	TopicUrl string `json:"url" binding:"omitempty,topicurl"`
}
func CreateTopic (id int, title string) Topic {
	return Topic{id, title}
}
// query参数绑定
type TopicQuery struct {
	UserName string `json:"username" form:"username"`
	Page int `json:"page" form:"page" binding:"required"`
	PageSize int `json:"pagesize" form:"pagesize"`
}:. form决定了绑定query参数的key到底是什么.没写form,则不会绑定.. 内置验证器是第三方库github.com/go-playground/validator
  • 9
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值