go doudou应用中使用注解示例详解

 更新时间:2022年12月07日 10:29:43   作者:武斌  
这篇文章主要为大家介绍了go doudou应用中使用注解示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

快速上手

我们都知道go语言没有原生的注解,但是做业务开发有些时候没有注解确实不方便。go-doudou通过go语言标准库ast/parser实现了对注解的支持。b站配套视频教程地址:[golang] go-doudou微服务框架入门03-如何使用注解,如果喜欢看视频,可直接跟视频上手实践。

我们通过一个简单的基于go-doudou开发的服务来演示用法和效果。

准备

  • 本地安装最新版go-doudou CLI
go install -v github.com/unionj-cloud/go-doudou@v1.1.9

本地安装postman,用于测试接口:www.postman.com/

本地安装goland

初始化工程

我们的服务名称和模块名称都叫annotation

go-doudou svc init annotation

设计业务接口

go-doudou应用的接口定义文件是项目根路径下的svc.go文件。打开文件后按照如下代码修改:

package service
import "context"
//go:generate go-doudou svc http --handler --doc
type Annotation interface {
	// 此接口可公开访问,无需校验登录和权限
	GetGuest(ctx context.Context) (data string, err error)
	// 此接口只有登录用户有权访问
	// @role(USER,ADMIN)
	GetUser(ctx context.Context) (data string, err error)
	// 此接口只有管理员有权访问
	// @role(ADMIN)
	GetAdmin(ctx context.Context) (data string, err error)
}

@role(USER,ADMIN)@role(ADMIN)就是本文的主角。注解定义格式为:@注解名称(参数1,参数2,参数3...)。可以根据业务实际需求,自定义各种不同的注解,@role仅是一个例子,你还可以定义其他如@permission(create,update,del),以及无参数注解@inner()

生成代码

点击截图中左上角的绿色三角形,执行go:generate指令,生成接口路由和http handler相关代码,以及遵循OpenAPI 3.0规范的json文档。

我们重点看一下transport/httpsrv/handler.go文件。

package httpsrv
import (
	"net/http"
	ddmodel "github.com/unionj-cloud/go-doudou/framework/http/model"
)
// http handler接口
type AnnotationHandler interface {
	GetGuest(w http.ResponseWriter, r *http.Request)
	GetUser(w http.ResponseWriter, r *http.Request)
	GetAdmin(w http.ResponseWriter, r *http.Request)
}
// 接口路由
func Routes(handler AnnotationHandler) []ddmodel.Route {
	return []ddmodel.Route{
		{
			"GetGuest",
			"GET",
			"/guest",
			handler.GetGuest,
		},
		{
			"GetUser",
			"GET",
			"/user",
			handler.GetUser,
		},
		{
			"GetAdmin",
			"GET",
			"/admin",
			handler.GetAdmin,
		},
	}
}
// 在内存中存储解析出来的注解信息
// ddmodel.AnnotationStore是map[string][]Annotation类型的别名,
// 键是路由名称,值是注解结构体切片。注解结构体中存放了注解名称和参数切片,
// 下文我们实现的校验权限的中间件原理就是通过http.Request对象拿到路由名称,
// 然后用路由名称从RouteAnnotationStore中找出存储的注解结构体切片,
// 最后比对从内存数据源或外部数据源拿到的用户角色和注解结构体的参数切片中的元素
// 判断该用户是否有权限继续访问接口
var RouteAnnotationStore = ddmodel.AnnotationStore{
	"GetUser": {
		{
			Name: "@role",
			Params: []string{
				"USER",
				"ADMIN",
			},
		},
	},
	"GetAdmin": {
		{
			Name: "@role",
			Params: []string{
				"ADMIN",
			},
		},
	},
}

下载依赖

执行命令go mod tidy,下载项目依赖。此时,服务已经可以启动了,但是我们不急。下面我们要根据注解信息,编写中间件,实现我们依据用户角色控制访问权限的需求。

Auth中间件

本示例项目的登录凭证采用http basic的base64 token。我们打开transport/httpsrv/middleware.go文件,黏贴进去如下代码:

package httpsrv
import (
	"annotation/vo"
	"github.com/gorilla/mux"
	"github.com/unionj-cloud/go-doudou/toolkit/sliceutils"
	"net/http"
)
// vo.UserStore是map[Auth]RoleEnum的别名类型,键为用户名和密码构成的结构体,值为角色枚举
// 我们用userStore代表数据库
func Auth(userStore vo.UserStore) func(inner http.Handler) http.Handler {
	return func(inner http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			// 从http.Request中拿到路由名称
			currentRoute := mux.CurrentRoute(r)
			if currentRoute == nil {
				inner.ServeHTTP(w, r)
				return
			}
			routeName := currentRoute.GetName()
			// 查询该路由是否有关联的注解结构体切片
			// 如果没有,则放行
			if !RouteAnnotationStore.HasAnnotation(routeName, "@role") {
				inner.ServeHTTP(w, r)
				return
			}
			// 从请求头中提取并解析http basic用户名和密码
			user, pass, ok := r.BasicAuth()
			// 如果不成功,则禁止访问,返回401
			if !ok {
				w.Header().Set("WWW-Authenticate", `Basic realm="Provide user name and password"`)
				w.WriteHeader(401)
				w.Write([]byte("Unauthorised.\n"))
				return
			}
			// 从userStore中查询是否存在此用户
			role, exists := userStore[vo.Auth{user, pass}]
			// 如果不存在,则禁止访问,返回401
			if !exists {
				w.Header().Set("WWW-Authenticate", `Basic realm="Provide user name and password"`)
				w.WriteHeader(401)
				w.Write([]byte("Unauthorised.\n"))
				return
			}
			// 如果存在,则判断该接口是否允许该用户所属角色访问
			params := RouteAnnotationStore.GetParams(routeName, "@role")
			// 判断该路由的@role注解的参数切片中是否包含该用户的角色
			// 如果不包含,则禁止访问,返回403
			if !sliceutils.StringContains(params, role.StringGetter()) {
				w.WriteHeader(403)
				w.Write([]byte("Access denied\n"))
				return
			}
			// 如果包含,则放行
			inner.ServeHTTP(w, r)
		})
	}
}

至此,我们已经完成核心逻辑开发。最后我们只要把这个中间件加到go-doudou服务里即可。

修改main函数

package main
import (
	service "annotation"
	"annotation/config"
	"annotation/transport/httpsrv"
	"annotation/vo"
	ddhttp "github.com/unionj-cloud/go-doudou/framework/http"
)
func main() {
	conf := config.LoadFromEnv()
	svc := service.NewAnnotation(conf)
	handler := httpsrv.NewAnnotationHandler(svc)
	srv := ddhttp.NewDefaultHttpSrv()
	// 将上文编写的Auth中间件加入go-doudou服务中
	srv.AddMiddleware(httpsrv.Auth(vo.UserStore{
		vo.Auth{
			User: "guest",
			Pass: "guest",
		}: vo.GUEST,
		vo.Auth{
			User: "user",
			Pass: "user",
		}: vo.USER,
		vo.Auth{
			User: "admin",
			Pass: "admin",
		}: vo.ADMIN,
	}))
	srv.AddRoute(httpsrv.Routes(handler)...)
	srv.Run()
}

启动服务

启动服务有多种方式:

go-doudou内置启动命令(仅用于开发阶段):

go-doudou svc run

go run cmd/main.go

点击main函数左边的绿色 图表

测试效果

将生成的annotation_openapi3.json文件导入postman中即可测试。postman的用法超出了本文的范畴,此处只附上部分截图供参考。

注解实现原理

go-doudou实现注解的原理非常简单,就是通过go语言标准库ast/parser对接口定义文件svc.go文件中的源码进行解析,将注释块里的注解通过正则表达式提取出来,创建Annotation结构体实例,关联到对应的接口上,最后作为静态变量RouteAnnotationStore的值通过代码生成器输出到transport/httpsrv/handler.go文件里的,供开发者调用。

以下是提取注解的源码,供参考:

var reAnno = regexp.MustCompile(`@(\S+?)\((.*?)\)`)
func GetAnnotations(text string) []Annotation {
	if !reAnno.MatchString(text) {
		return nil
	}
	var annotations []Annotation
	matches := reAnno.FindAllStringSubmatch(text, -1)
	for _, item := range matches {
		name := fmt.Sprintf(`@%s`, item[1])
		var params []string
		if stringutils.IsNotEmpty(item[2]) {
			params = strings.Split(strings.TrimSpace(item[2]), ",")
		}
		annotations = append(annotations, Annotation{
			Name:   name,
			Params: params,
		})
	}
	return annotations
}

总结

本文通过一个快速上手实例,讲解了go-doudou注解特性的用法和原理。有任何疑问都可以在下方留言。示例源码地址:github.com/unionj-clou…

关于go-doudou的更多特性和用法请参考官方文档:go-doudou.unionj.cloud/

以上就是go doudou应用中使用注解示例详解的详细内容,更多关于go doudou应用注解的资料请关注脚本之家其它相关文章!

相关文章

  • 手把手带你走进Go语言之语法基础解析

    手把手带你走进Go语言之语法基础解析

    这篇文章主要介绍了手把手带你走进Go语言之语法基础,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-09-09
  • 详解go mod 使用方法

    详解go mod 使用方法

    golang 提供了 go mod命令来管理包,是go的一个模块管理工具,用来代替传统的GOPATH方案,本文给大家介绍go mod 使用方法,感兴趣的朋友一起看看吧
    2022-05-05
  • go语言LeetCode题解1030距离顺序排列矩阵单元格

    go语言LeetCode题解1030距离顺序排列矩阵单元格

    这篇文章主要为大家介绍了go语言LeetCode题解1030距离顺序排列矩阵单元格,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-12-12
  • golang中snappy的使用场合实例详解

    golang中snappy的使用场合实例详解

    在java 和go语言 大字符传达的时候, 采用snappy 压缩 解压缩是最好的方案。下面这篇文章主要给大家介绍了关于golang中snappy使用场合的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下。
    2017-12-12
  • Golang基础学习之map的示例详解

    Golang基础学习之map的示例详解

    哈希表是常见的数据结构,有的语言会将哈希称作字典或者映射,在Go中,哈希就是常见的数据类型map,本文就来聊聊Golang中map的相关知识吧
    2023-03-03
  • Go gRPC服务客户端流式RPC教程

    Go gRPC服务客户端流式RPC教程

    这篇文章主要为大家介绍了Go gRPC服务客户端流式RPC教程示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-06-06
  • Golang使用zlib压缩和解压缩字符串

    Golang使用zlib压缩和解压缩字符串

    本文给大家分享的是Golang使用zlib压缩和解压缩字符串的方法和示例,有需要的小伙伴可以参考下
    2017-02-02
  • Golang二进制文件混淆保护操作

    Golang二进制文件混淆保护操作

    这篇文章主要介绍了Golang二进制文件混淆保护操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • Go map底层实现与扩容规则和特性分类详细讲解

    Go map底层实现与扩容规则和特性分类详细讲解

    这篇文章主要介绍了Go map底层实现与扩容规则和特性,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2023-03-03
  • 一文教你学会Go中singleflight的使用

    一文教你学会Go中singleflight的使用

    缓存在项目中使用应该是非常频繁的,提到缓存只要了解过 singleflight ,基本都会用于缓存实现的一部分吧,下面就跟随小编一起来学习一下singleflight的使用吧
    2024-02-02

最新评论