go doudou应用中使用注解示例详解
作者:武斌 发布时间:2024-02-23 08:35:07
快速上手
我们都知道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
}
来源:https://juejin.cn/post/7116826614830202910


猜你喜欢
- 一、Beautiful Soup库简介BeautifulSoup4 是一个 HTML/XML 的解析器,主要的功能是解析和提取 HTML/X
- 最近媳妇工作上遇到一个重复性劳动,excel表格查重,重复的标记起来,问我能不能写个程序让它自动查重标记必须安排第一次正儿八经写python
- 一、简介wxPython是Python语言的一套优秀的GUI图形库,允许Python程序员很方便的创建完整的、功能键全的GUI用户界面。 w
- 在Python中解析XML文件也有Dom和Sax两种方式,这里先介绍如何是使用Dom解析XML,这一篇文章是Dom生成XML文件,下一篇文章
- 数据分析师肯定每天都被各种各样的数据数据报表搞得焦头烂额,老板的,运营的、产品的等等。而且大部分报表都是重复性的工作,这篇文章就是帮助大家如
- 前言大家好,我是空空star,本篇给大家分享一下通过Python的pytesseract库识别图片中的文字。本篇所用软件相关版本:macOS
- 1. 安装pip3yum install python34-pip2. 安装python34develyum install python3
- 一、基于socket实现的TCP客户端import socket # 建立socket对象# 参数一表示IP地址类型(AF_INE
- 1.安装时选择的自动安装,忘了用户名和密码导致现在试了几个Oracle默认用户名密码後(表格中附带默认用户名及密码),都提示无效的用户名、密
- 前言Python是一门实现数据可视化很好的语言,他们里面的很多库可以很好的画出图形,形象明了。今天我们就来说说:Pandas数据分析核心支持
- “'验证码'等于'流氓软件'”这句话本身存在逻辑问题,因为“验证码”并不是一个软件,而是软件里的一个功能。这
- 1.导入模块tkinter:ttk覆盖tkinter部分对象,ttk对tkinter进行了优化copy:深拷贝时需要用到copy模块tkin
- CategoricalDtype自定义排序当我们的透视表生成完毕后,有很多情况下需要我们对某列或某行值进行排序。排序有很多种方法。例如sor
- ECMAScript5为数组定义了5个迭代方法。每个方法都接收两个参数:要在每一项上运行的函数和(可选的)运行该函数的作用域对象(即影响th
- 基于Python中求和函数sum的用法详解今天在看《集体编程智慧》这本书的时候,看到一段Python代码,当时是百思不得其解,总觉得是书中排
- 查询速度慢的原因很多,常见如下几种: 1、没有索引或者没有用到索引(这是查询慢最常见的问题,是程序设计的缺陷) 2、I/O吞吐量小,形成了瓶
- # set 不支持索引和切片,是一个无需的不重复得到容器# 类似于字典,但是只有key 没有value# 创建集合dic1={}set1={
- 现在有这样一个需求,内网有一个数据库服务,需要将外网的数据库导入到内网数据库。将外网的数据库导出sql文件有700MB+,用MySQL自带的
- 事件是将JavaScript脚本与网页联系在一起的主要方式,是JavaScript中最重要的主题之一,深入理解事件的工作机制以及它们对性能的
- 首先建一个access 数据库,库中有一个URLINDEX表,其中URL和Keywords字段分别添加了索引,如下:URL &nb