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
猜你喜欢
- 0、背景shutil.move可以实现文件或者目录的移动。打印:import shutilhelp(shutil.move)# 打印如下:&
- 一:需重定义神经网络继续训练的方法1.训练代码import numpy as npimport tensorflow as tfx_data
- 我们用pycharm打开自己写的代码,当多个文件之间有相互依赖的关系的时候,import无法识别自己写的文件,但是我们写的文件又确实在同一个
- 下面是asp代码实现列出sql数据库中存储过程的功能,可自行添加其它功能:< HTML >< 
- 本文实例讲述了Python实现识别图片内容的方法。分享给大家供大家参考,具体如下:python识别图片内容。这里我的环境为windows64
- 一、装饰器的本质:装饰器(decorator)本质是函数闭包(function closure)的语法糖(Syntactic sugar)函
- 前言最近由于在寻找方向上迷失自我,准备了解更多的计算机视觉任务重的模型。看到语义分割任务重Unet一个有意思的模型,我准备来复现一下它。一、
- 为了查找这些存储过程,你可以花时间在互联网搜索,查看一些你还未知道的存储过程,也许在一两个小时您可能会发现你想要...也许你很幸运的找到,其
- 本文实例讲述了Selenium定位元素操作。分享给大家供大家参考,具体如下:Selenium是一个用于Web应用程序测试的工具。Seleni
- 有时候需要把文件中的数据放入到数组中,这里提供了一种方法,可以根据文件结尾的标记进行数据拆分,然后再把拆分的文件放入数组中# -*-codi
- 1、数据驱动介绍:@ddt.ddt(类装饰器,申明当前类使用ddt框架)@ddt.data(函数装饰器,用于给测试用例传递数据),支持传py
- 先前在DW教学-Dreamweaver量身打造Wordpress留言板(一) 教学文章中,已经成功的把前端留言机制与界面搞定了,虽然有了留言
- 今天有个脚本需要遍历获取某指定文件夹下面的所有文件,我记得很早前也实现过文件遍历和目录遍历的功能,于是找来看一看,嘿,不看不知道,看了吓一跳
- 印象中最早看老外个人网站就挺纳闷,怎么人家都没有www,这样也可以?经过不断尝试,我发现确实不录入www要快捷的多,但不清楚怎么能做到。几年
- 介绍:今天在使用django的时候忽然想用到,如何匹配多个关键字的操作,我们知道django有一个objects.filter()方法,我们
- numpy norm()函数求范数函数:norm(x, ord = None, axis = None, keepdims = False)
- 问:我最近升级了一个应用程序,使其可以在 SQL Server 2005 上运行。我利用了允许行长度超出 8,060 个字节这项功能,以便用
- 1、选取最适用的字段属性MySQL可以很好的支持大数据量的存取,但是一般说来,数据库中的表越小,在它上面执行的查询也就会越快。因此,在创建表
- </pre><pre name="code" class="javascript"
- python-docx库可用于创建和编辑Microsoft Word(.docx)文件。官方文档:链接地址备注:doc是微软的专有的文件格式