v1:
package main
import (
"github.com/gin-gonic/gin"
"gitlab.com/go-course-project/go13/vblog/apps/token/api"
token_impl "gitlab.com/go-course-project/go13/vblog/apps/token/impl"
user_impl "gitlab.com/go-course-project/go13/vblog/apps/user/impl"
)
func main() {
// user service impl
usvc := user_impl.NewUserServiceImpl()
// token service impl
tsvc := token_impl.NewTokenServiceImpl(usvc)
// api
TokenApiHander := api.NewTokenApiHandler(tsvc)
// Protocol
engine := gin.Default()
rr := engine.Group("/vblog/api/v1")
TokenApiHander.Registry(rr)
// 把Http协议服务器启动起来
if err := engine.Run(":8080"); err != nil {
panic(err)
}
}
当模块众多的时候, main里面 手动组装对象的难度很越来越大
控制反转(Inversion of Control,缩写为IoC)
1. 注册对象(采用 init()导入的方式来执行注册)
_ "gitlab.com/go-course-project/go13/vblog/apps/token/api"
_ "gitlab.com/go-course-project/go13/vblog/apps/token/impl"
_ "gitlab.com/go-course-project/go13/vblog/apps/user/impl"
ioc.Registry("user_service_impl", &UserServiceImpl{})
ioc.Registry("token_service_impl", &TokenServiceImpl{})
2. 没有依赖关系的管理, 每个对象自己去ioc获取自己依赖
// 怎么实现token.Service接口?
// 定义TokenServiceImpl来实现接口
type TokenServiceImpl struct {
// 依赖了一个数据库操作的链接池对象
db *gorm.DB
// 依赖user.Service, 没有 UserServiceImpl 具体实现
// 依赖接口,不要接口的具体实现
user user.Service
}
// 依赖的关系解决 分层2个阶段, 一个注册, 一个初始化(组件完善自己的依赖关系)
func (i *TokenServiceImpl) Init() {
// 先通过ioc获取对象, 然后再把对象断言成 你需要接口
// tsvc := token_impl.NewTokenServiceImpl(usvc) 都不要
i.user = ioc.Get("user_service_impl").(user.Service)
}
3. ioc 来完成的对象的初始化, 让每个注册的对象,去完成依赖的自主寻找
ioc.InitAllObject()
package apps
import (
// 注册所有内部服务模块, 无须对外暴露的服务, 用于内部依赖
_ "github.com/infraboard/mcenter/apps/counter/impl"
_ "github.com/infraboard/mcenter/apps/ip2region/impl"
// 引入第三方存储模块(Mongo)
_ "github.com/infraboard/mcube/v2/ioc/apps/oss/mongo"
// 注册所有GRPC服务模块, 暴露给框架GRPC服务器加载, 注意 导入有先后顺序
_ "github.com/infraboard/mcenter/apps/domain/impl"
_ "github.com/infraboard/mcenter/apps/endpoint/impl"
_ "github.com/infraboard/mcenter/apps/instance/impl"
_ "github.com/infraboard/mcenter/apps/label/impl"
_ "github.com/infraboard/mcenter/apps/namespace/impl"
_ "github.com/infraboard/mcenter/apps/notify/impl"
_ "github.com/infraboard/mcenter/apps/policy/impl"
_ "github.com/infraboard/mcenter/apps/resource/impl"
_ "github.com/infraboard/mcenter/apps/role/impl"
_ "github.com/infraboard/mcenter/apps/service/impl"
_ "github.com/infraboard/mcenter/apps/token/impl"
_ "github.com/infraboard/mcenter/apps/user/impl"
)
使用map[string]Object 来实现一个简易版本的ioc Ioc 实现
改造之前: token(controller) ---> user(controller)
// user service impl
usvc := user_impl.NewUserServiceImpl()
// token service impl
tsvc := token_impl.NewTokenServiceImpl(usvc)
改造:
// 通过Import 自动完成注册
// 为什么不能直接在这里把db对象给初始化了?
// 这个逻辑是在import时候执行的, 程序在import 后才,执行的配置模块加载, 此时的conf.C()为nil
// 这个db属性的初始化一定要在配置加载后执行: conf.Load(), ioc.Init()
func init() {
ioc.Controller().Registry(user.AppName, &UserServiceImpl{})
}
func init() {
ioc.Controller().Registry(token.AppName, &TokenServiceImpl{})
}
// 对象属性初始化
func (i *TokenServiceImpl) Init() error {
i.db = conf.C().DB()
i.user = ioc.Controller().Get(user.AppName).(user.Service)
return nil
}
改造之前: token(api) ---> token(controller)
// token service impl
tsvc := token_impl.NewTokenServiceImpl(usvc)
// api
TokenApiHander := api.NewTokenApiHandler(tsvc)
改造之后: TokenApiHander的注册, ioc.Init() 来执行依赖获取
func init() {
ioc.Api().Registry(token.AppName, &TokenApiHandler{})
}
func (h *TokenApiHandler) Init() error {
// 从Controller空间中 获取 模块的具体实现
// 断言该实现是满足该接口的
h.svc = ioc.Controller().Get(token.AppName).(token.Service)
return nil
}
之前的启动方法:
func main() {
// user service impl
usvc := user_impl.NewUserServiceImpl()
// token service impl
tsvc := token_impl.NewTokenServiceImpl(usvc)
// api
TokenApiHander := api.NewTokenApiHandler(tsvc)
// Protocol
engine := gin.Default()
rr := engine.Group("/vblog/api/v1")
TokenApiHander.Registry(rr)
// 把Http协议服务器启动起来
if err := engine.Run(":8080"); err != nil {
panic(err)
}
}
之后的代码:
import (
"github.com/gin-gonic/gin"
"gitlab.com/go-course-project/go13/vblog/apps/token"
"gitlab.com/go-course-project/go13/vblog/apps/token/api"
"gitlab.com/go-course-project/go13/vblog/conf"
"gitlab.com/go-course-project/go13/vblog/ioc"
// 2.1 先注册对象
_ "gitlab.com/go-course-project/go13/vblog/apps/token/impl"
_ "gitlab.com/go-course-project/go13/vblog/apps/user/impl"
_ "gitlab.com/go-course-project/go13/vblog/apps/token/api"
)
func main() {
// 1. 初始化程序配置, 这里没有配置,使用默认值
if err := conf.LoadFromEnv(); err != nil {
panic(err)
}
// 2.2 程序对象管理
if err := ioc.Init(); err != nil {
panic(err)
}
// Protocol
engine := gin.Default()
rr := engine.Group("/vblog/api/v1")
// ioc 能不能帮忙把 模块API的注册也一起管理
// ioc.Init() , 对象初始化完成后, 如果对象是 api对象,就帮忙完成下注册
// ioc.RegistryGin(rr)?
ioc.Api().Get(token.AppName).(*api.TokenApiHandler).Registry(rr)
// 把Http协议服务器启动起来
if err := engine.Run(":8080"); err != nil {
panic(err)
}
}
type GinApi interface {
// 基础约束
Object
// 额外约束
// ioc.Api().Get(token.AppName).(*api.TokenApiHandler).Registry(rr)
Registry(rr gin.IRouter)
}
// 遍历所有的对象, 帮忙完成Api的注册
// 由ioc调用对象提供的 Registry方法,来吧模块的api 注册给gin root router
func (c *NamespaceContainer) RegistryGinApi(rr gin.IRouter) {
// 遍历Namespace
for key := range c.ns {
c := c.ns[key]
// 遍历Namespace里面的对象
for objectName := range c.storage {
obj := c.storage[objectName]
// 如果判断一个对象是不是GinApi对象(约束)
// 判断对象有没有Registry(rr gin.IRouter)
// 断言该对象 满足GinApi接口, 实现了Registry函数
if v, ok := obj.(GinApi); ok {
v.Registry(rr)
}
}
}
}
改造后,我们只需要完成业务对象注册, 其他操作 ioc帮忙进行管理
func main() {
// 1. 初始化程序配置, 这里没有配置,使用默认值
if err := conf.LoadFromEnv(); err != nil {
panic(err)
}
// 2. 程序对象管理
if err := ioc.Init(); err != nil {
panic(err)
}
// Protocol
engine := gin.Default()
rr := engine.Group("/vblog/api/v1")
ioc.RegistryGinApi(rr)
// 把Http协议服务器启动起来
if err := engine.Run(":8080"); err != nil {
panic(err)
}
}
通过 导入"gitlab.com/go-course-project/go13/vblog/apps" 来导入所有的业务实现
package apps
// 注册业务实现: API + Controller
import (
// 通过import方法 完成注册
_ "gitee.com/baicaijc/vblog/apps/token/api"
_ "gitee.com/baicaijc/vblog/apps/token/impl"
_ "gitee.com/baicaijc/vblog/apps/user/impl"
)
入口就不需要修改:
package main
import (
"gitee.com/baicaijc/vblog/conf"
"gitee.com/baicaijc/vblog/ioc"
"github.com/gin-gonic/gin"
// 2.1 通过import方法 完成注册
_ "gitee.com/baicaijc/vblog/apps"
)
func main() {
// 1. 初始化程序配置, 这里没有配置,使用默认值
if err := conf.LoadFromFile("conf/etc/application.toml"); err != nil {
panic(err)
}
// 2.2 程序对象管理
if err := ioc.Init(); err != nil {
panic(err)
}
// Protocol
engine := gin.Default()
rr := engine.Group("/vblog/api/v1")
// ioc.Api().Get(token.AppName).(*api.TokenApiHandler).Registry(rr)
ioc.RegistryGinApi(rr)
// 把Http协议服务器启动起来
if err := engine.Run(":8080"); err != nil {
panic(err)
}
}
有了ioc后,我们业务开发流程, 编写:
// Blog Service接口定义, CRUD
type Service interface {
// 创建一个博客
CreateBlog(context.Context, *CreateBlogRequest) (*Blog, error)
// 获取博客列表
QueryBlog(context.Context, *QueryBlogRequest) (*BlogSet, error)
// 获取博客详情
DescribeBlog(context.Context, *DescribeBlogRequest) (*Blog, error)
// 更新博客
UpdateBlog(context.Context, *UpdateBlogRequest) (*Blog, error)
// 删除博客
DeleteBlog(context.Context, *DeleteBlogRequest) (*Blog, error)
// 文章状态修改, 比如发布
ChangedBlogStatus(context.Context, *ChangedBlogStatusRequest) (*Blog, error)
// 文章审核
AuditBlog(context.Context, *AuditInfo) (*Blog, error)
}
func init() {
ioc.Controller().Registry(blog.AppName, &blogServiceImpl{})
}
// blog.Service接口, 是直接注册给Ioc, 不需要对我暴露
type blogServiceImpl struct {
// 依赖了一个数据库操作的链接池对象
db *gorm.DB
}
func (i *blogServiceImpl) Init() error {
i.db = conf.C().DB()
return nil
}
func (i *blogServiceImpl) Destory() error {
return nil
}
// 注册业务实现: API + Controller
import (
_ "gitee.com/baicaijc/vblog/apps/blog/impl"
)
package impl
import (
"context"
"gitee.com/baicaijc/vblog/apps/blog"
)
// 创建一个博客
func (i *blogServiceImpl) CreateBlog(ctx context.Context, req *blog.CreateBlogRequest) (*blog.Blog, error) {
return nil, nil
}
// 获取博客列表
func (i *blogServiceImpl) QueryBlog(ctx context.Context, in *blog.QueryBlogRequest) (*blog.BlogSet, error) {
return nil, nil
}
// 获取博客详情
func (i *blogServiceImpl) DescribeBlog(ctx context.Context, in *blog.DescribeBlogRequest) (*blog.Blog, error) {
return nil, nil
}
// 删除博客
func (i *blogServiceImpl) DeleteBlog(ctx context.Context, req *blog.DeleteBlogRequest) (*blog.Blog, error) {
return nil, nil
}
// 更新博客
// 1. 全新更新: 对象的替换
// 2. 部分更新: (old obj) --patch--> new obj ---> save
func (i *blogServiceImpl) UpdateBlog(ctx context.Context, req *blog.UpdateBlogRequest) (*blog.Blog, error) {
return nil, nil
}
// 文章状态修改, 比如发布
func (i *blogServiceImpl) ChangedBlogStatus(ctx context.Context, req *blog.ChangedBlogStatusRequest) (*blog.Blog, error) {
return nil, nil
}
// 文章审核
func (i *blogServiceImpl) AuditBlog(ctx context.Context, req *blog.AuditInfo) (*blog.Blog, error) {
return nil, nil
}
4.1 编写单元测试: 准备被测试的对象
package impl_test
import (
"context"
"gitee.com/baicaijc/vblog/apps/blog"
"gitee.com/baicaijc/vblog/ioc"
// 1. 加载对象
_ "gitee.com/baicaijc/vblog/apps"
)
// blog service 的实现的具体对象是在ioc中,
// 需要在ioc中获取具体的svc 用来测试
var (
impl blog.Service
ctx = context.Background()
)
func init() {
// 2. ioc获取对象
impl = ioc.Controller().Get(blog.AppName).(blog.Service)
// ioc需要初始化 才能填充 db属性
if err := ioc.Init(); err != nil {
panic(err)
}
}
4.2 编写单元测试: 测试接口实现
package impl_test
import (
"testing"
"gitee.com/baicaijc/vblog/apps/blog"
)
func TestCreateBlog(t *testing.T) {
req := blog.NewCreateBlogRequest()
req.Title = "go语言全栈开发"
req.Author = "oldyu"
req.Content = "xxx"
req.Summary = "xx"
req.Tags["目录"] = "Go语言"
ins, err := impl.CreateBlog(ctx, nil)
if err != nil {
t.Fatal(err)
}
t.Log(ins)
}
// 创建一个博客
func (i *blogServiceImpl) CreateBlog(ctx context.Context, req *blog.CreateBlogRequest) (*blog.Blog, error) {
// 1. 校验请求
if err := req.Validate(); err != nil {
return nil, exception.ErrBadRequest.WithMessagef("创建博客失败, %s", err)
}
// 2. 构造对象
ins := blog.NewBlog(req)
// 3. 对象入库
// INSERT INTO `blogs` (`created_at`,`updated_at`,`title`,`author`,`content`,`summary`,`create_by`,`tags`,`published_at`,`status`,`audit_at`,`is_audit_pass`) VALUES (1706340774,1706340774,'go语言全栈开发','oldyu','xxx','xx','','{"目录":"Go语言"}',0,'0',0,false)
err := i.db.WithContext(ctx).Create(ins).Error
if err != nil {
return nil, err
}
// 4. 返回对象
return ins, nil
}
关于Mergo
// 更新博客
// 1. 全新更新: 对象的替换
// 2. 部分更新: (old obj) --patch--> new obj ---> save
func (i *blogServiceImpl) UpdateBlog(ctx context.Context, req *blog.UpdateBlogRequest) (*blog.Blog, error) {
// 查询老的对象, 需要被更新的博客对象
ins, err := i.DescribeBlog(ctx, blog.NewDescribeBlogRequest(req.Id))
if err != nil {
return nil, err
}
// 对象更新
switch req.UpdateMode {
case common.UPDATE_MODE_PATCH:
// if req.Author != "" {
// ins.Author = req.Author
// }
// if req.Title != "" {
// ins.Title = req.Title
// }
//... 有没有其他的办法 帮我们完成2个结构图的合并 merge(patch)
// https://github.com/darccio/mergo
// // WithOverride will make merge override non-empty dst attributes with non-empty src attributes values.
//
if err := mergo.MapWithOverwrite(ins.CreateBlogRequest, req.CreateBlogRequest); err != nil {
return nil, err
}
default:
ins.CreateBlogRequest = req.CreateBlogRequest
}
// 再次校验对象, 校验更新后的数据是否合法
if err := ins.Validate(); err != nil {
return nil, exception.ErrBadRequest.WithMessagef("校验更新请求失败: %s", err)
}
// 更新数据库
// UPDATE `blogs` SET `id`=48,`created_at`=1706344163,`updated_at`=1706344423,`title`='go语言全栈开发V2',`author`='oldyu',`content`='xxx',`summary`='xx',`tags`='{"目录":"Go语言"}' WHERE id = 48
err = i.db.WithContext(ctx).Model(&blog.Blog{}).Where("id = ?", ins.Id).Updates(ins).Error
if err != nil {
return nil, err
}
return ins, nil
}
func init() {
ioc.Api().Registry(blog.AppName, &blogApiHandler{})
}
// blog.Service接口, 是直接注册给Ioc, 不需要对我暴露
type blogApiHandler struct {
svc blog.Service
}
func (i *blogApiHandler) Init() error {
i.svc = ioc.Controller().Get(blog.AppName).(blog.Service)
return nil
}
func (i *blogApiHandler) Destory() error {
return nil
}
// 注册业务实现: API + Controller
import (
_ "gitlab.com/go-course-project/go13/vblog/apps/blog/api"
)
// + 创建博客: POST /vblogs/api/v1/blogs
func (h *blogApiHandler) CreateBlog(c *gin.Context) {
// h.tk.Validate()
req := blog.NewCreateBlogRequest()
// // 后面请求如何获取 中间信息
// if v, ok := c.Get(token.TOKEN_MIDDLEWARE_KEY); ok {
// req.CreateBy = v.(*token.Token).UserName
// }
if err := c.BindJSON(req); err != nil {
response.Failed(c, err)
return
}
ins, err := h.svc.CreateBlog(c.Request.Context(), req)
if err != nil {
response.Failed(c, err)
return
}
response.Success(c, ins)
}
// + 修改博客(部分): PATCH /vblogs/api/v1/blogs/:id
// /vblogs/api/v1/blogs/10 --> id = 10
// /vblogs/api/v1/blogs/20 --> id = 20
// c.Param("id") 获取路径变量的值
func (h *blogApiHandler) PatchBlog(c *gin.Context) {
// h.tk.Validate()
// 如果解析路径里面的参数
req := blog.NewUpdateBlogRequest(c.Param("id"))
req.UpdateMode = common.UPDATE_MODE_PATCH
// 用户传递的数据
if err := c.BindJSON(req.CreateBlogRequest); err != nil {
response.Failed(c, err)
return
}
ins, err := h.svc.UpdateBlog(c.Request.Context(), req)
if err != nil {
response.Failed(c, err)
return
}
response.Success(c, ins)
}
// + 修改博客(全量): PUT /vblogs/api/v1/blogs/:id
func (h *blogApiHandler) UpdateBlog(c *gin.Context) {
// 如何解析路径里面的参数
req := blog.NewUpdateBlogRequest(c.Param("id"))
req.UpdateMode = common.UPDATE_MODE_PUT
// 用户传递的数据
if err := c.BindJSON(req.CreateBlogRequest); err != nil {
response.Failed(c, err)
return
}
ins, err := h.svc.UpdateBlog(c.Request.Context(), req)
if err != nil {
response.Failed(c, err)
return
}
response.Success(c, ins)
}
// + 删除博客: DELETE /vblogs/api/v1/blogs/:id
func (h *blogApiHandler) DeleteBlog(c *gin.Context) {
req := blog.NewDeleteBlogRequest(c.Param("id"))
ins, err := h.svc.DeleteBlog(c.Request.Context(), req)
if err != nil {
response.Failed(c, err)
return
}
response.Success(c, ins)
}
// + 查询列表: GET /vblogs/api/v1/blogs?page_size=10&page_number=2
func (h *blogApiHandler) QueryBlog(c *gin.Context) {
req := blog.NewQueryBlogRequestFromGin(c)
set, err := h.svc.QueryBlog(c.Request.Context(), req)
if err != nil {
response.Failed(c, err)
return
}
response.Success(c, set)
}
// + 查询详情: GET /vblogs/api/v1/blogs/:id
func (h *blogApiHandler) DescribeBlog(c *gin.Context) {
req := blog.NewDescribeBlogRequest(c.Param("id"))
ins, err := h.svc.DescribeBlog(c.Request.Context(), req)
if err != nil {
response.Failed(c, err)
return
}
response.Success(c, ins)
}
// 让ioc帮我们完成接口的路由注册 ioc.GinApi
//
// type GinApi interface {
// // 基础约束
// Object
// // 额外约束
// // ioc.Api().Get(token.AppName).(*api.TokenApiHandler).Registry(rr)
// Registry(rr gin.IRouter)
// }
func (i *blogApiHandler) Registry(rr gin.IRouter) {
r := rr.Group(blog.AppName)
r.POST("/", i.CreateBlog)
r.GET("/", i.QueryBlog)
r.GET("/:id", i.DescribeBlog)
r.PATCH("/:id", i.PatchBlog)
r.PUT("/:id", i.UpdateBlog)
r.DELETE("/:id", i.DeleteBlog)
}
现在写的blog api 是没有权限认证的
可以单独在每个请求内部去校验逻辑:
// + 创建博客: POST /vblogs/api/v1/blogs
func (h *blogApiHandler) CreateBlog(c *gin.Context) {
// h.tk.Validate()
...
}
使用中间件来补充中间件:
中间件是一个函数, 这个函数参数的定义是 框架定义
use(middleware)
// HandlerFunc defines the handler used by gin middleware as return value.
type HandlerFunc func(*Context)
import (
"github.com/gin-gonic/gin"
"gitlab.com/go-course-project/go13/vblog/apps/token"
"gitlab.com/go-course-project/go13/vblog/ioc"
"gitlab.com/go-course-project/go13/vblog/response"
)
func Auth(c *gin.Context) {
// 获取tk 模块
svc := ioc.Controller().Get(token.AppName).(token.Service)
ak := token.GetAccessTokenFromHttp(c.Request)
req := token.NewValidateTokenRequest(ak)
tk, err := svc.ValidateToken(c.Request.Context(), req)
if err != nil {
response.Failed(c, err)
return
}
// 请求通过, 用户的身份信息 携带在 请求的上下文当中,传递给后续请求
// 对于Gin, 如果把请求的中间数据传递下,使用了一个map, 这个map在request对象上
c.Keys[token.TOKEN_MIDDLEWARE_KEY] = tk
// 后面请求如何获取 中间信息
// c.Keys[token.TOKEN_MIDDLEWARE_KEY].(*token.Token).UserName
}
func (i *blogApiHandler) Registry(rr gin.IRouter) {
r := rr.Group(blog.AppName)
// 普通接口, 允许访客使用, 不需要权限
r.GET("/", i.QueryBlog)
r.GET("/:id", i.DescribeBlog)
// 整个模块后面的请求 都需求认证
r.Use(middleware.Auth)
// 后面管理的接口 需要权限
r.POST("/", i.CreateBlog)
r.PATCH("/:id", i.PatchBlog)
r.PUT("/:id", i.UpdateBlog)
r.DELETE("/:id", i.DeleteBlog)
}
curl --location 'localhost:8080/vblog/api/v1/blogs'
curl --location 'localhost:8080/vblog/api/v1/blogs/48'
curl --location '127.0.0.1:8080/vblog/api/v1/blogs' \
--header 'Content-Type: application/json' \
--header 'Cookie: token=cmqd7apus0na8uhinjl0' \
--data '{
"title": "Go全线开发",
"author": "老喻",
"content": "Go可以"
}'
用户关联角色,角色关联具体功能
// required(vistor,admin)
// 方式一
// r.use(role_name, ...)
// r.GET('/', h.GetBlog)
// 方式二
// r.GET('/', h.Required(visitor), h.GetBLog)
// 鉴权失败, 认证通过,但是没有权限操作 该接口
ErrPermissionDeny = NewAPIException(http.StatusForbidden, http.StatusText(http.StatusForbidden)).WithHttpCode(http.StatusForbidden)
package middleware
import (
"fmt"
"gitee.com/baicaijc/vblog/apps/token"
"gitee.com/baicaijc/vblog/apps/user"
"gitee.com/baicaijc/vblog/exception"
"gitee.com/baicaijc/vblog/response"
"github.com/gin-gonic/gin"
)
// Required(user.ROLE_ADMIN) ---> gin.HandlerFunc
func Required(roles ...user.Role) gin.HandlerFunc {
return func(c *gin.Context) {
// 检验用户的角色
// 直接通过上下文取出Token, 通过Token获取用户角色,来定义访问这个接口的角色
// 后面请求如何获取 中间信息
if v, ok := c.Get(token.TOKEN_MIDDLEWARE_KEY); ok {
// 遍历判断 用户是否在运行的角色列表里面
hasPerm := false
for _, r := range roles {
fmt.Println(roles, v.(*token.Token).Role)
if r == v.(*token.Token).Role {
hasPerm = true
}
}
if !hasPerm {
response.Failed(c, exception.ErrPermissionDeny.WithMessagef("允许访问的角色: %v", roles))
return
}
} else {
response.Failed(c, exception.ErrUnauthorized)
return
}
}
}
// 3. 补充用户角色
u, err := i.user.DescribeUser(ctx, user.NewDescribeUserRequest(tk.UserId))
if err != nil {
return nil, err
}
tk.Role = u.Role
// 定义 ChipType 类型的方法 String(), 返回字符串。
func (r Role) String() string {
switch r {
case ROLE_VISITOR:
return "member"
case ROLE_ADMIN:
return "admin"
default:
return "NA"
}
}
// 只允许管理员才能删除
r.DELETE("/:id", middleware.Required(user.ROLE_ADMIN), i.DeleteBlog)
使用访客账号来测试 文章的删除
curl --location --request DELETE '127.0.0.1:8080/vblog/api/v1/blogs/1' \
--header 'Cookie: token=cn8c3s5u48h2g3432300'
{
"code": 403,
"reason": "Forbidden",
"message": "允许访问的角色: [admin]"
}
权限是控制 用户对某个功能 是否可以使用, 比如 文件编辑接口, 不能让说有的人 对所有的文章都能编辑, 只希望每个作者只能编辑自己的文章
就需要对接口的访问范围做控制
只访问 属于自己的文章, username=?
参数获取create_by
func NewQueryBlogRequestFromGin(c *gin.Context) *QueryBlogRequest {
req := NewQueryBlogRequest()
req.CreateBy = c.Query("create_by")
ps := c.Query("page_size")
if ps != "" {
req.PageSize, _ = strconv.Atoi(ps)
}
pn := c.Query("page_number")
if pn != "" {
req.PageNumber, _ = strconv.Atoi(pn)
}
return req
}
type QueryBlogRequest struct {
// 分页大小, 一个多少个
PageSize int
// 当前页, 查询哪一页的数据
PageNumber int
// 谁创建的文章
CreateBy string
}
实现时,带上过滤条件
// 获取博客列表
// 获取博客列表
func (i *blogServiceImpl) QueryBlog(ctx context.Context, in *blog.QueryBlogRequest) (*blog.BlogSet, error) {
set := blog.NewBlogSet()
// 1. 初始化查询对象
query := i.db.WithContext(ctx).Model(blog.Blog{})
// 补充查询条件
if in.CreateBy != "" {
query = query.Where("create_by = ?", in.CreateBy)
}
// 查询总算
err := query.Count(&set.Total).Error
if err != nil {
return nil, err
}
// 查询具体的数据
err = query.
Limit(in.Limit()).
Offset(in.Offset()).
Find(&set.Items).
Error
if err != nil {
return nil, err
}
return set, nil
}
带着这个请求的请求文章, create_by就是过滤条件, 如果做成通用的, 就成了资源的scope
/vblog/api/v1/blogs?create_by="usera"
在编辑的时候,强制控制 访问数据的访问是 用户自己的
强制补充scope CreateBy
// + 修改博客(全量): PUT /vblogs/api/v1/blogs/:id
func (h *blogApiHandler) UpdateBlog(c *gin.Context) {
// 如果解析路径里面的参数
req := blog.NewUpdateBlogRequest(c.Param("id"))
req.UpdateMode = common.UPDATE_MODE_PUT
// 用户传递的数据
if err := c.BindJSON(req.CreateBlogRequest); err != nil {
response.Failed(c, err)
return
}
// 后面请求如何获取 中间信息
if v, ok := c.Get(token.TOKEN_MIDDLEWARE_KEY); ok {
req.CreateBy = v.(*token.Token).UserName
}
ins, err := h.svc.UpdateBlog(c.Request.Context(), req)
if err != nil {
response.Failed(c, err)
return
}
response.Success(c, ins)
}
补充 Scope访问控制
// 更新数据库
// UPDATE `blogs` SET `id`=48,`created_at`=1706344163,`updated_at`=1706344423,`title`='go语言全栈开发V2',`author`='oldyu',`content`='xxx',`summary`='xx',`tags`='{"目录":"Go语言"}' WHERE id = 48
stmt := i.db.WithContext(ctx).Model(&blog.Blog{}).Where("id = ?", ins.Id)
if req.CreateBy != "" {
stmt = stmt.Where("create_by = ?", ins.CreateBy)
}
如何初始化Root/管理员用户 是个问题? (Admin 来创建用户)
需要程序提供初始化的功能:
vblog init : 初始化admin用户
vblog start: 启动程序
// 带有子命令的CLI
kubectl pods list
kubectl pods delete
go run main.go
由cli为我们程序提供多个执行的入口:
就是cli的根, 常用于:
docker -v
Docker version 25.0.1, build 29cf629
// vblog init
var rootCmd = &cobra.Command{
Use: "hugo",
Short: "Hugo is a very fast static site generator",
Long: `A Fast and Flexible Static Site Generator built with
love by spf13 and friends in Go.
Complete documentation is available at https://gohugo.io/documentation/`,
Run: func(cmd *cobra.Command, args []string) {
// Do Stuff Here
},
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
定义Root
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
var rootCmd = &cobra.Command{
Use: "vblog",
Short: "vblog api server",
Run: func(cmd *cobra.Command, args []string) {
// 什么都不做的时候打印帮助信息
cmd.Help()
},
}
func Execute() {
// 注册Root命令的子命令
rootCmd.AddCommand(initCmd, startCmd)
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
迁移start命令
package cmd
import (
"gitee.com/baicaijc/vblog/conf"
"gitee.com/baicaijc/vblog/ioc"
"github.com/gin-gonic/gin"
"github.com/spf13/cobra"
)
var startCmd = &cobra.Command{
Use: "start",
Short: "启动服务器",
Run: func(cmd *cobra.Command, args []string) {
// 什么都不做的时候打印帮助信息
// 1. 初始化程序配置, 这里没有配置,使用默认值
if err := conf.LoadFromEnv(); err != nil {
panic(err)
}
// 2. 程序对象管理
if err := ioc.Init(); err != nil {
panic(err)
}
// Protocol
engine := gin.Default()
rr := engine.Group("/vblog/api/v1")
ioc.RegistryGinApi(rr)
// 把Http协议服务器启动起来
if err := engine.Run(":8080"); err != nil {
panic(err)
}
},
}
定义init命令:
package cmd
import "github.com/spf13/cobra"
var initCmd = &cobra.Command{
Use: "init",
Short: "程序初始化",
Run: func(cmd *cobra.Command, args []string) {
// 什么都不做的时候打印帮助信息
},
}
执行Root Cmd
package main
import (
// 1 初始化配置
"gitee.com/baicaijc/vblog/cmd"
_ "gitee.com/baicaijc/vblog/conf"
// 2.1 通过import方法 完成注册
_ "gitee.com/baicaijc/vblog/apps"
)
func main() {
// // 2.2 程序对象管理
// if err := ioc.Init(); err != nil {
// panic(err)
// }
// // Protocol
// engine := gin.Default()
// rr := engine.Group("/vblog/api/v1")
// // ioc.Api().Get(token.AppName).(*api.TokenApiHandler).Registry(rr)
// ioc.RegistryGinApi(rr)
// // 把Http协议服务器启动起来
// if err := engine.Run(":8080"); err != nil {
// panic(err)
// }
cmd.Execute()
}
启动CLI
go run main.go start
// 编译后再运行
// go build -o vblog main.go
// vblog start
var initCmd = &cobra.Command{
Use: "init",
Short: "程序初始化",
Run: func(cmd *cobra.Command, args []string) {
// 什么都不做的时候打印帮助信息
// 程序对象管理
cobra.CheckErr(ioc.Init())
// 需要初始化 管理员用户
// 使用构造函数创建请求对象
// user.CreateUserRequest{}
req := user.NewCreateUserRequest()
req.Username = "admin"
req.Password = xid.New().String()
req.Role = user.ROLE_ADMIN
fmt.Println("用户名: ", req.Username)
fmt.Println("密码 : ", req.Password)
u, err := ioc.Controller().Get(user.AppName).(user.Service).CreateUser(
cmd.Context(),
req,
)
cobra.CheckErr(err)
// 正常打印对象
fmt.Println(u)
},
}
/*
PS E:\浏览器下载\mage_course\vblog> go run main.go init
用户名: admin
密码 : cn91gkdu48h3cl7dp6ig
2024/02/18 22:46:41 E:/浏览器下载/mage_course/vblog/apps/user/impl/user.go:37
[4.857ms] [rows:1] INSERT INTO `users` (`created_at`,`updated_at`,`username`,`password`,`role`,`label`) VALUES (1708267601,1708267601,'admin','$2a$10$XnUNDaZQ/IVuiLl7SrNR/uHDOFIpdgLlccciMJdBf34tN2urUSljm',1,'{}')
{
"id": 15,
"created_at": 1708267601,
"updated_at": 1708267601,
"username": "admin",
"password": "$2a$10$XnUNDaZQ/IVuiLl7SrNR/uHDOFIpdgLlccciMJdBf34tN2urUSljm",
"role": 1,
"label": {}
}
PS E:\浏览器下载\mage_course\vblog>
*/
# 初始化管理员用户
vblog init
# 再启动服务
vblog start
Makefile文件(windows 自己安装下 windows下的make)
PKG := "gitee.com/baicaijc/vblog"
dep: ## Get the dependencies
@go mod tidy
run: ## Run Server
@go run main.go start
help: ## Display this help screen
@grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
➜ vblog git:(main) ✗ make help
dep Get the dependencies
run Run Server
help Display this help screen
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。