From 9eb977cfbf9c3f5851666e06f17e3d7915561689 Mon Sep 17 00:00:00 2001 From: yumaojun03 <719118794@qq.com> Date: Sun, 8 Jun 2025 15:11:14 +0800 Subject: [PATCH] update role --- devcloud/mcenter/apps/endpoint/enum.go | 2 + devcloud/mcenter/apps/role/api_permission.go | 102 +++++++ devcloud/mcenter/apps/role/design.drawio | 19 ++ devcloud/mcenter/apps/role/enum.go | 18 ++ .../mcenter/apps/role/impl/api_permission.go | 110 ++++++++ .../apps/role/impl/api_permission_test.go | 48 ++++ devcloud/mcenter/apps/role/impl/imp_test.go | 18 ++ devcloud/mcenter/apps/role/impl/impl.go | 31 ++ devcloud/mcenter/apps/role/impl/role.go | 103 +++++++ devcloud/mcenter/apps/role/impl/role_test.go | 59 ++++ .../mcenter/apps/role/impl/view_permission.go | 82 ++++++ devcloud/mcenter/apps/role/interface.go | 265 ++++++++++++++++++ devcloud/mcenter/apps/role/model.go | 66 +++++ devcloud/mcenter/apps/role/view_permission.go | 45 +++ devcloud/mcenter/apps/token/impl/impl_test.go | 2 +- .../token/issuers/password/issuer_test.go | 2 +- .../issuers/private_token/issueer_test.go | 2 +- devcloud/mcenter/apps/user/impl/impl_test.go | 2 +- devcloud/mcenter/apps/view/model.go | 60 ++++ devcloud/mcenter/apps/view/page.go | 64 +++++ devcloud/mcenter/design.drawio | 14 +- devcloud/mcenter/test/set_up.go | 2 +- go.mod | 2 +- homework/README.md | 6 +- 24 files changed, 1109 insertions(+), 15 deletions(-) create mode 100644 devcloud/mcenter/apps/role/api_permission.go create mode 100644 devcloud/mcenter/apps/role/design.drawio create mode 100644 devcloud/mcenter/apps/role/enum.go create mode 100644 devcloud/mcenter/apps/role/impl/api_permission.go create mode 100644 devcloud/mcenter/apps/role/impl/api_permission_test.go create mode 100644 devcloud/mcenter/apps/role/impl/imp_test.go create mode 100644 devcloud/mcenter/apps/role/impl/impl.go create mode 100644 devcloud/mcenter/apps/role/impl/role.go create mode 100644 devcloud/mcenter/apps/role/impl/role_test.go create mode 100644 devcloud/mcenter/apps/role/impl/view_permission.go create mode 100644 devcloud/mcenter/apps/role/interface.go create mode 100644 devcloud/mcenter/apps/role/model.go create mode 100644 devcloud/mcenter/apps/role/view_permission.go create mode 100644 devcloud/mcenter/apps/view/model.go create mode 100644 devcloud/mcenter/apps/view/page.go diff --git a/devcloud/mcenter/apps/endpoint/enum.go b/devcloud/mcenter/apps/endpoint/enum.go index ea9f083..d1826a8 100644 --- a/devcloud/mcenter/apps/endpoint/enum.go +++ b/devcloud/mcenter/apps/endpoint/enum.go @@ -3,7 +3,9 @@ package endpoint type ACCESS_MODE uint8 const ( + // 只读模式 ACCESS_MODE_READ = iota + // 读写模式 ACCESS_MODE_READ_WRITE ) diff --git a/devcloud/mcenter/apps/role/api_permission.go b/devcloud/mcenter/apps/role/api_permission.go new file mode 100644 index 0000000..3941869 --- /dev/null +++ b/devcloud/mcenter/apps/role/api_permission.go @@ -0,0 +1,102 @@ +package role + +import ( + "122.51.31.227/go-course/go18/devcloud/mcenter/apps/endpoint" + "github.com/infraboard/mcube/v2/tools/pretty" + "github.com/infraboard/modules/iam/apps" +) + +func NewApiPermission(roleId uint64, spec *ApiPermissionSpec) *ApiPermission { + return &ApiPermission{ + ResourceMeta: *apps.NewResourceMeta(), + RoleId: roleId, + ApiPermissionSpec: *spec, + } +} + +type ApiPermission struct { + // 基础数据 + apps.ResourceMeta + // 角色Id + RoleId uint64 `json:"role_id" gorm:"column:role_id;index" description:"角色Id"` + // Api权限定义 + ApiPermissionSpec +} + +func (r *ApiPermission) TableName() string { + return "api_permissions" +} + +func (r *ApiPermission) String() string { + return pretty.ToJSON(r) +} + +func NewResourceActionApiPermissionSpec(service, resource, action string) *ApiPermissionSpec { + return &ApiPermissionSpec{ + Extras: map[string]string{}, + MatchBy: MATCH_BY_RESOURCE_ACTION, + Service: service, + Resource: resource, + Action: action, + } +} + +type ApiPermissionSpec struct { + // 创建者ID + CreateBy uint64 `json:"create_by" gorm:"column:create_by" description:"创建者ID" optional:"true"` + // 角色描述 + Description string `json:"description" gorm:"column:description;type:text" bson:"description" description:"角色描述"` + // 权限匹配方式 + MatchBy MATCH_BY `json:"match_by" gorm:"column:match_by;type:tinyint(1);index" bson:"match_by" description:"权限匹配方式"` + // MATCH_BY_ID 时指定的 Endpoint Id + EndpointId *uint64 `json:"endpoint_id" gorm:"column:endpoint_id;type:uint;index"` + // 操作标签 + Label string `json:"label" gorm:"column:label;type:varchar(200);index"` + // 服务 + Service string `json:"service" gorm:"column:service;type:varchar(100);index" bson:"service" description:"服务名称"` + // 资源列表 + Resource string `json:"resource" gorm:"column:resource;type:varchar(100);index" bson:"resource" description:"资源名称"` + // 资源操作 + Action string `json:"action" bson:"action" gorm:"column:action;type:varchar(100);index"` + // 读或者读写 + AccessMode endpoint.ACCESS_MODE `json:"access_mode" bson:"access_mode" gorm:"column:access_mode;type:tinyint(1);index"` + + // 其他扩展信息 + Extras map[string]string `json:"extras" gorm:"column:extras;serializer:json;type:json" description:"其他扩展信息" optional:"true"` +} + +func (a *ApiPermissionSpec) GetEndpointId() uint64 { + if a.EndpointId == nil { + return 0 + } + return *a.EndpointId +} + +// 判断是否有当前API的访问权限 +func (a *ApiPermissionSpec) IsMatch(target *endpoint.Endpoint) bool { + switch a.MatchBy { + case MATCH_BY_ID: + if a.EndpointId == nil { + return false + } + if *a.EndpointId == target.Id { + return true + } + case MATCH_BY_RESOURCE_ACCESS_MODE: + if a.AccessMode == target.AccessMode { + return true + } + case MATCH_BY_RESOURCE_ACTION: + if a.Service != "*" && a.Service != target.Service { + return false + } + if a.Resource != "*" && a.Resource != target.Resource { + return false + } + if a.Action != "*" && a.Action != target.Action { + return false + } + return true + } + return false +} diff --git a/devcloud/mcenter/apps/role/design.drawio b/devcloud/mcenter/apps/role/design.drawio new file mode 100644 index 0000000..2d7cb36 --- /dev/null +++ b/devcloud/mcenter/apps/role/design.drawio @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/devcloud/mcenter/apps/role/enum.go b/devcloud/mcenter/apps/role/enum.go new file mode 100644 index 0000000..a7cda63 --- /dev/null +++ b/devcloud/mcenter/apps/role/enum.go @@ -0,0 +1,18 @@ +package role + +const ( + ADMIN = "admin" +) + +type MATCH_BY int32 + +const ( + // 针对某一个具体的接口进行授权 + MATCH_BY_ID = iota + // 通过标签来进行 API接口授权 + MATCH_BY_LABLE + // 通过资源和动作来进行授权, user::list + MATCH_BY_RESOURCE_ACTION + // 通过资源的访问模式进行授权 + MATCH_BY_RESOURCE_ACCESS_MODE +) diff --git a/devcloud/mcenter/apps/role/impl/api_permission.go b/devcloud/mcenter/apps/role/impl/api_permission.go new file mode 100644 index 0000000..a1e5fc3 --- /dev/null +++ b/devcloud/mcenter/apps/role/impl/api_permission.go @@ -0,0 +1,110 @@ +package impl + +import ( + "context" + + "122.51.31.227/go-course/go18/devcloud/mcenter/apps/endpoint" + "122.51.31.227/go-course/go18/devcloud/mcenter/apps/role" + "github.com/infraboard/mcube/v2/exception" + "github.com/infraboard/mcube/v2/ioc/config/datasource" + "github.com/infraboard/mcube/v2/types" + "gorm.io/gorm" +) + +// 添加角色关联API +func (i *RoleServiceImpl) AddApiPermission(ctx context.Context, in *role.AddApiPermissionRequest) ([]*role.ApiPermission, error) { + if err := in.Validate(); err != nil { + return nil, exception.NewBadRequest("validate add api permission error, %s", err) + } + + perms := []*role.ApiPermission{} + if err := datasource.DBFromCtx(ctx).Transaction(func(tx *gorm.DB) error { + for i := range in.Items { + item := in.Items[i] + perm := role.NewApiPermission(in.RoleId, item) + if err := tx.Save(perm).Error; err != nil { + return err + } + perms = append(perms, perm) + } + return nil + }); err != nil { + return nil, err + } + + return perms, nil +} + +// 查询角色关联的权限条目 +func (i *RoleServiceImpl) QueryApiPermission(ctx context.Context, in *role.QueryApiPermissionRequest) ([]*role.ApiPermission, error) { + query := datasource.DBFromCtx(ctx).Model(&role.ApiPermission{}) + if len(in.RoleIds) > 0 { + query = query.Where("role_id IN ?", in.RoleIds) + } + if len(in.ApiPermissionIds) > 0 { + query = query.Where("id IN ?", in.ApiPermissionIds) + } + + perms := []*role.ApiPermission{} + if err := query. + Order("created_at desc"). + Find(&perms).Error; err != nil { + return nil, err + } + return perms, nil +} + +// 移除角色关联API +func (i *RoleServiceImpl) RemoveApiPermission(ctx context.Context, in *role.RemoveApiPermissionRequest) ([]*role.ApiPermission, error) { + if err := in.Validate(); err != nil { + return nil, err + } + perms, err := i.QueryApiPermission(ctx, role.NewQueryApiPermissionRequest().AddRoleId(in.RoleId).AddPermissionId(in.ApiPermissionIds...)) + if err != nil { + return nil, err + } + + if err := datasource.DBFromCtx(ctx). + Where("role_id = ?", in.RoleId). + Where("id IN ?", in.ApiPermissionIds). + Delete(&role.ApiPermission{}). + Error; err != nil { + return nil, err + } + + return perms, nil +} + +// 查询匹配到的Api接口列表 +func (i *RoleServiceImpl) QueryMatchedEndpoint(ctx context.Context, in *role.QueryMatchedEndpointRequest) (*types.Set[*endpoint.Endpoint], error) { + set := types.New[*endpoint.Endpoint]() + + // 查询角色的权限 + perms, err := i.QueryApiPermission(ctx, role.NewQueryApiPermissionRequest().AddRoleId(in.RoleIds...)) + if err != nil { + return nil, err + } + + // 查询服务的Endpoint列表 + endpointReq := endpoint.NewQueryEndpointRequest() + for _, perm := range perms { + endpointReq.WithService(perm.Service) + } + endpoints, err := endpoint.GetService().QueryEndpoint(ctx, endpointReq) + if err != nil { + return nil, err + } + + // 找出能匹配的API + endpoints.ForEach(func(t *endpoint.Endpoint) { + for _, perm := range perms { + if perm.IsMatch(t) { + if !endpoint.IsEndpointExist(set, t) { + set.Add(t) + } + } + } + }) + + return set, nil +} diff --git a/devcloud/mcenter/apps/role/impl/api_permission_test.go b/devcloud/mcenter/apps/role/impl/api_permission_test.go new file mode 100644 index 0000000..97d1dc3 --- /dev/null +++ b/devcloud/mcenter/apps/role/impl/api_permission_test.go @@ -0,0 +1,48 @@ +package impl_test + +import ( + "testing" + + "122.51.31.227/go-course/go18/devcloud/mcenter/apps/role" +) + +func TestQueryApiPermission(t *testing.T) { + req := role.NewQueryApiPermissionRequest() + req.AddRoleId(2) + set, err := impl.QueryApiPermission(ctx, req) + if err != nil { + t.Fatal(err) + } + t.Log(set) +} + +func TestAddApiPermission(t *testing.T) { + req := role.NewAddApiPermissionRequest(1) + req.Add(role.NewResourceActionApiPermissionSpec("devcloud", "user", "list")) + set, err := impl.AddApiPermission(ctx, req) + if err != nil { + t.Fatal(err) + } + t.Log(set) +} + +func TestQueryMatchedEndpoint(t *testing.T) { + req := role.NewQueryMatchedEndpointRequest() + req.Add(1) + set, err := impl.QueryMatchedEndpoint(ctx, req) + if err != nil { + t.Fatal(err) + } + t.Log(set) +} + +func TestRemoveApiPermission(t *testing.T) { + req := role.NewRemoveApiPermissionRequest(2) + req.Add(2) + set, err := impl.RemoveApiPermission(ctx, req) + if err != nil { + t.Fatal(err) + } + + t.Log(set) +} diff --git a/devcloud/mcenter/apps/role/impl/imp_test.go b/devcloud/mcenter/apps/role/impl/imp_test.go new file mode 100644 index 0000000..8ae453e --- /dev/null +++ b/devcloud/mcenter/apps/role/impl/imp_test.go @@ -0,0 +1,18 @@ +package impl_test + +import ( + "context" + + "122.51.31.227/go-course/go18/devcloud/mcenter/apps/role" + "122.51.31.227/go-course/go18/devcloud/mcenter/test" +) + +var ( + impl role.Service + ctx = context.Background() +) + +func init() { + test.DevelopmentSetUp() + impl = role.GetService() +} diff --git a/devcloud/mcenter/apps/role/impl/impl.go b/devcloud/mcenter/apps/role/impl/impl.go new file mode 100644 index 0000000..5815382 --- /dev/null +++ b/devcloud/mcenter/apps/role/impl/impl.go @@ -0,0 +1,31 @@ +package impl + +import ( + "122.51.31.227/go-course/go18/devcloud/mcenter/apps/role" + "github.com/infraboard/mcube/v2/ioc" + "github.com/infraboard/mcube/v2/ioc/config/datasource" +) + +func init() { + ioc.Controller().Registry(&RoleServiceImpl{}) +} + +var _ role.Service = (*RoleServiceImpl)(nil) + +type RoleServiceImpl struct { + ioc.ObjectImpl +} + +func (i *RoleServiceImpl) Init() error { + if datasource.Get().AutoMigrate { + err := datasource.DB().AutoMigrate(&role.Role{}, &role.ApiPermission{}, &role.ViewPermission{}) + if err != nil { + return err + } + } + return nil +} + +func (i *RoleServiceImpl) Name() string { + return role.AppName +} diff --git a/devcloud/mcenter/apps/role/impl/role.go b/devcloud/mcenter/apps/role/impl/role.go new file mode 100644 index 0000000..2956187 --- /dev/null +++ b/devcloud/mcenter/apps/role/impl/role.go @@ -0,0 +1,103 @@ +package impl + +import ( + "context" + + "122.51.31.227/go-course/go18/devcloud/mcenter/apps/role" + "github.com/infraboard/mcube/v2/exception" + "github.com/infraboard/mcube/v2/ioc/config/datasource" + "github.com/infraboard/mcube/v2/types" + "gorm.io/gorm" +) + +// 创建角色 +func (i *RoleServiceImpl) CreateRole(ctx context.Context, in *role.CreateRoleRequest) (*role.Role, error) { + if err := in.Validate(); err != nil { + return nil, err + } + + ins := role.NewRole() + ins.CreateRoleRequest = *in + + if err := datasource.DBFromCtx(ctx). + Create(ins). + Error; err != nil { + return nil, err + } + return ins, nil +} + +// 列表查询 +func (i *RoleServiceImpl) QueryRole(ctx context.Context, in *role.QueryRoleRequest) (*types.Set[*role.Role], error) { + set := types.New[*role.Role]() + + query := datasource.DBFromCtx(ctx).Model(&role.Role{}) + if len(in.RoleIds) > 0 { + query = query.Where("id IN ?", in.RoleIds) + in.PageSize = uint64(len(in.RoleIds)) + } + err := query.Count(&set.Total).Error + if err != nil { + return nil, err + } + + err = query. + Order("created_at desc"). + Offset(int(in.ComputeOffset())). + Limit(int(in.PageSize)). + Find(&set.Items). + Error + if err != nil { + return nil, err + } + return set, nil +} + +// 详情查询 +func (i *RoleServiceImpl) DescribeRole(ctx context.Context, in *role.DescribeRoleRequest) (*role.Role, error) { + query := datasource.DBFromCtx(ctx) + + ins := &role.Role{} + if err := query.Where("id = ?", in.Id).First(ins).Error; err != nil { + if err == gorm.ErrRecordNotFound { + return nil, exception.NewNotFound("role %d not found", in.Id) + } + return nil, err + } + + pm, err := i.QueryApiPermission(ctx, role.NewQueryApiPermissionRequest().AddRoleId(in.Id)) + if err != nil { + return nil, err + } + ins.ApiPermissions = pm + + return ins, nil +} + +// 更新角色 +func (i *RoleServiceImpl) UpdateRole(ctx context.Context, in *role.UpdateRoleRequest) (*role.Role, error) { + descReq := role.NewDescribeRoleRequest() + descReq.SetId(in.Id) + ins, err := i.DescribeRole(ctx, descReq) + if err != nil { + return nil, err + } + + ins.CreateRoleRequest = in.CreateRoleRequest + return ins, datasource.DBFromCtx(ctx).Where("id = ?", in.Id).Updates(ins).Error +} + +// 删除角色 +func (i *RoleServiceImpl) DeleteRole(ctx context.Context, in *role.DeleteRoleRequest) (*role.Role, error) { + descReq := role.NewDescribeRoleRequest() + descReq.SetId(in.Id) + ins, err := i.DescribeRole(ctx, descReq) + if err != nil { + return nil, err + } + + return ins, datasource.DBFromCtx(ctx). + Where("id = ?", in.Id). + Delete(&role.Role{}). + Error +} diff --git a/devcloud/mcenter/apps/role/impl/role_test.go b/devcloud/mcenter/apps/role/impl/role_test.go new file mode 100644 index 0000000..2ddc771 --- /dev/null +++ b/devcloud/mcenter/apps/role/impl/role_test.go @@ -0,0 +1,59 @@ +package impl_test + +import ( + "testing" + + "122.51.31.227/go-course/go18/devcloud/mcenter/apps/role" +) + +func TestQueryRole(t *testing.T) { + req := role.NewQueryRoleRequest() + set, err := impl.QueryRole(ctx, req) + if err != nil { + t.Fatal(err) + } + t.Log(set) +} + +func TestDescribeRole(t *testing.T) { + req := role.NewDescribeRoleRequest() + req.SetId(1) + ins, err := impl.DescribeRole(ctx, req) + if err != nil { + t.Fatal(err) + } + t.Log(ins) +} + +func TestCreateAdminRole(t *testing.T) { + req := role.NewCreateRoleRequest() + req.Name = "admin" + req.Description = "管理员" + ins, err := impl.CreateRole(ctx, req) + if err != nil { + t.Fatal(err) + } + t.Log(ins) +} + +func TestCreateGuestRole(t *testing.T) { + req := role.NewCreateRoleRequest() + req.Name = "guest" + req.Description = "访客" + ins, err := impl.CreateRole(ctx, req) + if err != nil { + t.Fatal(err) + } + t.Log(ins) +} + +func TestCreateDevRole(t *testing.T) { + req := role.NewCreateRoleRequest() + req.Name = "dev" + req.Description = "开发" + ins, err := impl.CreateRole(ctx, req) + if err != nil { + t.Fatal(err) + } + t.Log(ins) +} diff --git a/devcloud/mcenter/apps/role/impl/view_permission.go b/devcloud/mcenter/apps/role/impl/view_permission.go new file mode 100644 index 0000000..f79e79b --- /dev/null +++ b/devcloud/mcenter/apps/role/impl/view_permission.go @@ -0,0 +1,82 @@ +package impl + +import ( + "context" + + "122.51.31.227/go-course/go18/devcloud/mcenter/apps/role" + "122.51.31.227/go-course/go18/devcloud/mcenter/apps/view" + "github.com/infraboard/mcube/v2/exception" + "github.com/infraboard/mcube/v2/ioc/config/datasource" + "github.com/infraboard/mcube/v2/types" + "gorm.io/gorm" +) + +// 添加角色关联菜单 +func (i *RoleServiceImpl) AddViewPermission(ctx context.Context, in *role.AddViewPermissionRequest) ([]*role.ViewPermission, error) { + if err := in.Validate(); err != nil { + return nil, exception.NewBadRequest("validate add view permission error, %s", err) + } + + perms := []*role.ViewPermission{} + if err := datasource.DBFromCtx(ctx).Transaction(func(tx *gorm.DB) error { + for i := range in.Items { + item := in.Items[i] + perm := role.NewViewPermission(in.RoleId, item) + if err := tx.Save(perm).Error; err != nil { + return err + } + perms = append(perms, perm) + } + return nil + }); err != nil { + return nil, err + } + + return perms, nil +} + +// 查询角色关联的视图权限 +func (i *RoleServiceImpl) QueryViewPermission(ctx context.Context, in *role.QueryViewPermissionRequest) ([]*role.ViewPermission, error) { + query := datasource.DBFromCtx(ctx).Model(&role.ViewPermission{}) + if len(in.RoleIds) > 0 { + query = query.Where("role_id IN ?", in.RoleIds) + } + if len(in.ViewPermissionIds) > 0 { + query = query.Where("in IN ?", in.ViewPermissionIds) + } + + perms := []*role.ViewPermission{} + if err := query.Order("created_at desc"). + Where("id IN ?", in.RoleIds). + Find(&perms).Error; err != nil { + return nil, err + } + return perms, nil +} + +// 移除角色关联菜单 +func (i *RoleServiceImpl) RemoveViewPermission(ctx context.Context, in *role.RemoveViewPermissionRequest) ([]*role.ViewPermission, error) { + if err := in.Validate(); err != nil { + return nil, err + } + + perms, err := i.QueryViewPermission(ctx, role.NewQueryViewPermissionRequest().AddRoleId(in.RoleId).AddPermissionId(in.ViewPermissionIds...)) + if err != nil { + return nil, err + } + + if err := datasource.DBFromCtx(ctx). + Where("role_id = ?", in.RoleId). + Where("id IN ?", in.ViewPermissionIds). + Delete(&role.ViewPermission{}). + Error; err != nil { + return nil, err + } + + return perms, nil +} + +// 查询能匹配到视图菜单 +func (i *RoleServiceImpl) QueryMatchedPage(ctx context.Context, in *role.QueryMatchedPageRequest) (*types.Set[*view.Menu], error) { + return nil, nil +} diff --git a/devcloud/mcenter/apps/role/interface.go b/devcloud/mcenter/apps/role/interface.go new file mode 100644 index 0000000..c077a61 --- /dev/null +++ b/devcloud/mcenter/apps/role/interface.go @@ -0,0 +1,265 @@ +package role + +import ( + "context" + "slices" + + "122.51.31.227/go-course/go18/devcloud/mcenter/apps/endpoint" + "122.51.31.227/go-course/go18/devcloud/mcenter/apps/view" + "github.com/infraboard/mcube/v2/http/request" + "github.com/infraboard/mcube/v2/ioc" + "github.com/infraboard/mcube/v2/ioc/config/validator" + "github.com/infraboard/mcube/v2/types" + "github.com/infraboard/modules/iam/apps" +) + +const ( + AppName = "role" +) + +func GetService() Service { + return ioc.Controller().Get(AppName).(Service) +} + +type Service interface { + RoleService + ApiPermissionService + ViewPermissionService +} + +// 角色管理 +type RoleService interface { + // 创建角色 + CreateRole(context.Context, *CreateRoleRequest) (*Role, error) + // 列表查询 + QueryRole(context.Context, *QueryRoleRequest) (*types.Set[*Role], error) + // 详情查询 + DescribeRole(context.Context, *DescribeRoleRequest) (*Role, error) + // 更新角色 + UpdateRole(context.Context, *UpdateRoleRequest) (*Role, error) + // 删除角色 + DeleteRole(context.Context, *DeleteRoleRequest) (*Role, error) +} + +func NewQueryRoleRequest() *QueryRoleRequest { + return &QueryRoleRequest{ + PageRequest: request.NewDefaultPageRequest(), + RoleIds: []uint64{}, + } +} + +type QueryRoleRequest struct { + *request.PageRequest + WithMenuPermission bool `json:"with_menu_permission" form:"with_menu_permission"` + WithApiPermission bool `json:"with_api_permission" form:"with_api_permission"` + RoleIds []uint64 `json:"role_ids" form:"role_ids"` +} + +func (r *QueryRoleRequest) AddRoleId(roleIds ...uint64) *QueryRoleRequest { + for _, rid := range roleIds { + if !slices.Contains(r.RoleIds, rid) { + r.RoleIds = append(r.RoleIds, rid) + } + } + return r +} + +func NewDescribeRoleRequest() *DescribeRoleRequest { + return &DescribeRoleRequest{} +} + +type DescribeRoleRequest struct { + apps.GetRequest +} + +type UpdateRoleRequest struct { + apps.GetRequest + CreateRoleRequest +} + +func NewDeleteRoleRequest() *DeleteRoleRequest { + return &DeleteRoleRequest{} +} + +type DeleteRoleRequest struct { + apps.GetRequest +} + +// 角色API接口管理 +type ApiPermissionService interface { + // 查询角色关联的权限条目 + QueryApiPermission(context.Context, *QueryApiPermissionRequest) ([]*ApiPermission, error) + // 添加角色关联API + AddApiPermission(context.Context, *AddApiPermissionRequest) ([]*ApiPermission, error) + // 移除角色关联API + RemoveApiPermission(context.Context, *RemoveApiPermissionRequest) ([]*ApiPermission, error) + // 查询匹配到的Api接口列表 + QueryMatchedEndpoint(context.Context, *QueryMatchedEndpointRequest) (*types.Set[*endpoint.Endpoint], error) +} + +func NewQueryApiPermissionRequest() *QueryApiPermissionRequest { + return &QueryApiPermissionRequest{ + RoleIds: []uint64{}, + ApiPermissionIds: []uint64{}, + } +} + +type QueryApiPermissionRequest struct { + RoleIds []uint64 `json:"role_ids"` + ApiPermissionIds []uint64 `json:"api_permission_ids"` +} + +func (r *QueryApiPermissionRequest) AddRoleId(roleIds ...uint64) *QueryApiPermissionRequest { + r.RoleIds = append(r.RoleIds, roleIds...) + return r +} + +func (r *QueryApiPermissionRequest) AddPermissionId(permissionIds ...uint64) *QueryApiPermissionRequest { + r.ApiPermissionIds = append(r.ApiPermissionIds, permissionIds...) + return r +} + +func NewQueryMatchedEndpointRequest() *QueryMatchedEndpointRequest { + return &QueryMatchedEndpointRequest{ + RoleIds: []uint64{}, + } +} + +type QueryMatchedEndpointRequest struct { + RoleIds []uint64 `json:"role_ids" form:"role_ids"` +} + +func (r *QueryMatchedEndpointRequest) Add(roleIds ...uint64) *QueryMatchedEndpointRequest { + for _, rid := range roleIds { + if !slices.Contains(r.RoleIds, rid) { + r.RoleIds = append(r.RoleIds, rid) + } + } + return r +} + +func NewAddApiPermissionRequest(roleId uint64) *AddApiPermissionRequest { + return &AddApiPermissionRequest{ + RoleId: roleId, + } +} + +type AddApiPermissionRequest struct { + RoleId uint64 `json:"role_id"` + Items []*ApiPermissionSpec `json:"items"` +} + +func (r *AddApiPermissionRequest) Validate() error { + return validator.Validate(r) +} + +func (r *AddApiPermissionRequest) Add(specs ...*ApiPermissionSpec) *AddApiPermissionRequest { + r.Items = append(r.Items, specs...) + return r +} + +func NewRemoveApiPermissionRequest(roleId uint64) *RemoveApiPermissionRequest { + return &RemoveApiPermissionRequest{ + RoleId: roleId, + ApiPermissionIds: []uint64{}, + } +} + +type RemoveApiPermissionRequest struct { + RoleId uint64 `json:"role_id"` + ApiPermissionIds []uint64 `json:"api_permission_ids"` +} + +func (r *RemoveApiPermissionRequest) Add(apiPermissionIds ...uint64) *RemoveApiPermissionRequest { + r.ApiPermissionIds = append(r.ApiPermissionIds, apiPermissionIds...) + return r +} + +func (r *RemoveApiPermissionRequest) Validate() error { + return validator.Validate(r) +} + +type UpdateApiPermissionRequest struct { + Items []*ApiPermission `json:"items"` +} + +// 角色菜单管理 +type ViewPermissionService interface { + // 查询角色关联的视图权限 + QueryViewPermission(context.Context, *QueryViewPermissionRequest) ([]*ViewPermission, error) + // 添加角色关联菜单 + AddViewPermission(context.Context, *AddViewPermissionRequest) ([]*ViewPermission, error) + // 移除角色关联菜单 + RemoveViewPermission(context.Context, *RemoveViewPermissionRequest) ([]*ViewPermission, error) + // 查询能匹配到视图菜单 + QueryMatchedPage(context.Context, *QueryMatchedPageRequest) (*types.Set[*view.Menu], error) +} + +func NewQueryViewPermissionRequest() *QueryViewPermissionRequest { + return &QueryViewPermissionRequest{ + RoleIds: []uint64{}, + ViewPermissionIds: []uint64{}, + } +} + +type QueryViewPermissionRequest struct { + RoleIds []uint64 `json:"role_ids"` + ViewPermissionIds []uint64 `json:"view_permission_ids"` +} + +func (r *QueryViewPermissionRequest) AddRoleId(roleIds ...uint64) *QueryViewPermissionRequest { + r.RoleIds = append(r.RoleIds, roleIds...) + return r +} + +func (r *QueryViewPermissionRequest) AddPermissionId(permissionIds ...uint64) *QueryViewPermissionRequest { + r.ViewPermissionIds = append(r.ViewPermissionIds, permissionIds...) + return r +} + +func NewQueryMatchedPageRequest() *QueryMatchedPageRequest { + return &QueryMatchedPageRequest{} +} + +type QueryMatchedPageRequest struct { + apps.GetRequest +} + +func NewAddViewPermissionRequest() *AddViewPermissionRequest { + return &AddViewPermissionRequest{ + Items: []*ViewPermissionSpec{}, + } +} + +type AddViewPermissionRequest struct { + RoleId uint64 `json:"role_id"` + Items []*ViewPermissionSpec `json:"items"` +} + +func (r *AddViewPermissionRequest) Validate() error { + return validator.Validate(r) +} + +func (r *AddViewPermissionRequest) Add(specs ...*ViewPermissionSpec) *AddViewPermissionRequest { + r.Items = append(r.Items, specs...) + return r +} + +type UpdateViewPermission struct { + Items []ViewPermission `json:"items"` +} + +func NewRemoveViewPermissionRequest() *RemoveViewPermissionRequest { + return &RemoveViewPermissionRequest{ + ViewPermissionIds: []uint64{}, + } +} + +type RemoveViewPermissionRequest struct { + RoleId uint64 `json:"role_id"` + ViewPermissionIds []uint64 `json:"menu_permission_ids"` +} + +func (r *RemoveViewPermissionRequest) Validate() error { + return validator.Validate(r) +} diff --git a/devcloud/mcenter/apps/role/model.go b/devcloud/mcenter/apps/role/model.go new file mode 100644 index 0000000..87c1589 --- /dev/null +++ b/devcloud/mcenter/apps/role/model.go @@ -0,0 +1,66 @@ +package role + +import ( + "122.51.31.227/go-course/go18/devcloud/mcenter/apps/endpoint" + "github.com/infraboard/mcube/v2/ioc/config/validator" + "github.com/infraboard/mcube/v2/tools/pretty" + "github.com/infraboard/modules/iam/apps" +) + +func NewRole() *Role { + return &Role{ + ResourceMeta: *apps.NewResourceMeta(), + MenuPermissions: []*ViewPermission{}, + ApiPermissions: []*ApiPermission{}, + } +} + +type Role struct { + // 基础数据 + apps.ResourceMeta + // 角色创建信息 + CreateRoleRequest + // 菜单权限 + MenuPermissions []*ViewPermission `json:"menu_permissions,omitempty" gorm:"-" description:"角色关联的菜单权限"` + // API权限 + ApiPermissions []*ApiPermission `json:"api_permissions,omitempty" gorm:"-" description:"角色关联的API权限"` +} + +func (r *Role) TableName() string { + return "roles" +} + +func (r *Role) String() string { + return pretty.ToJSON(r) +} + +// 该角色是否允许该API访问 +func (r *Role) CheckPerm(re *endpoint.RouteEntry) error { + return nil +} + +func NewCreateRoleRequest() *CreateRoleRequest { + return &CreateRoleRequest{ + Extras: map[string]string{}, + Enabled: true, + } +} + +type CreateRoleRequest struct { + // 创建者ID + CreateBy uint64 `json:"create_by" gorm:"column:create_by" description:"创建者ID" optional:"true"` + // 角色名称 + Name string `json:"name" gorm:"column:name;type:varchar(100);index" bson:"name" description:"角色名称"` + // 角色描述 + Description string `json:"description" gorm:"column:description;type:text" bson:"description" description:"角色描述"` + // 是否启用 + Enabled bool `json:"enabled" bson:"enabled" gorm:"column:enabled;type:tinyint(1)" description:"是否启用" optional:"true"` + // 标签 + Label string `json:"label" gorm:"column:label;type:varchar(200);index" description:"标签" optional:"true"` + // 其他扩展信息 + Extras map[string]string `json:"extras" gorm:"column:extras;serializer:json;type:json" description:"其他扩展信息" optional:"true"` +} + +func (r *CreateRoleRequest) Validate() error { + return validator.Validate(r) +} diff --git a/devcloud/mcenter/apps/role/view_permission.go b/devcloud/mcenter/apps/role/view_permission.go new file mode 100644 index 0000000..43f124a --- /dev/null +++ b/devcloud/mcenter/apps/role/view_permission.go @@ -0,0 +1,45 @@ +package role + +import ( + "github.com/infraboard/modules/iam/apps" +) + +func NewViewPermission(roleId uint64, spec *ViewPermissionSpec) *ViewPermission { + return &ViewPermission{ + ResourceMeta: *apps.NewResourceMeta(), + RoleId: roleId, + ViewPermissionSpec: *spec, + } +} + +type ViewPermission struct { + // 基础数据 + apps.ResourceMeta + // 角色Id + RoleId uint64 `json:"role_id" gorm:"column:role_id;index" description:"Role Id"` + // Menu权限定义 + ViewPermissionSpec +} + +func (r *ViewPermission) TableName() string { + return "view_permissions" +} + +func NewViewPermissionSpec() *ViewPermissionSpec { + return &ViewPermissionSpec{ + Extras: map[string]string{}, + } +} + +type ViewPermissionSpec struct { + // 创建者ID + CreateBy uint64 `json:"create_by" gorm:"column:create_by" description:"创建者ID" optional:"true"` + // 权限描述 + Description string `json:"description" gorm:"column:description;type:text" bson:"description" description:"角色描述"` + // 页面路径 + PagePath string `json:"path_path" gorm:"column:path_path;type:varchar(200);index" bson:"path_path" description:"页面路径(可以通配)"` + // 组件名称 + Components []string `json:"components" gorm:"column:components;type:json;serializer:json" bson:"components" description:"页面组件(可以通配)"` + // 其他扩展信息 + Extras map[string]string `json:"extras" gorm:"column:extras;serializer:json;type:json" description:"其他扩展信息" optional:"true"` +} diff --git a/devcloud/mcenter/apps/token/impl/impl_test.go b/devcloud/mcenter/apps/token/impl/impl_test.go index 4a96a50..20033fc 100644 --- a/devcloud/mcenter/apps/token/impl/impl_test.go +++ b/devcloud/mcenter/apps/token/impl/impl_test.go @@ -13,6 +13,6 @@ var ( ) func init() { - test.DevelopmentSet() + test.DevelopmentSetUp() svc = token.GetService() } diff --git a/devcloud/mcenter/apps/token/issuers/password/issuer_test.go b/devcloud/mcenter/apps/token/issuers/password/issuer_test.go index b1b5555..fb6a374 100644 --- a/devcloud/mcenter/apps/token/issuers/password/issuer_test.go +++ b/devcloud/mcenter/apps/token/issuers/password/issuer_test.go @@ -18,5 +18,5 @@ func TestPasswordIssuer(t *testing.T) { } func init() { - test.DevelopmentSet() + test.DevelopmentSetUp() } diff --git a/devcloud/mcenter/apps/token/issuers/private_token/issueer_test.go b/devcloud/mcenter/apps/token/issuers/private_token/issueer_test.go index 6a64dd7..14bc93c 100644 --- a/devcloud/mcenter/apps/token/issuers/private_token/issueer_test.go +++ b/devcloud/mcenter/apps/token/issuers/private_token/issueer_test.go @@ -18,5 +18,5 @@ func TestPasswordIssuer(t *testing.T) { } func init() { - test.DevelopmentSet() + test.DevelopmentSetUp() } diff --git a/devcloud/mcenter/apps/user/impl/impl_test.go b/devcloud/mcenter/apps/user/impl/impl_test.go index 8fde448..16935c0 100644 --- a/devcloud/mcenter/apps/user/impl/impl_test.go +++ b/devcloud/mcenter/apps/user/impl/impl_test.go @@ -13,6 +13,6 @@ var ( ) func init() { - test.DevelopmentSet() + test.DevelopmentSetUp() impl = user.GetService() } diff --git a/devcloud/mcenter/apps/view/model.go b/devcloud/mcenter/apps/view/model.go new file mode 100644 index 0000000..2c40099 --- /dev/null +++ b/devcloud/mcenter/apps/view/model.go @@ -0,0 +1,60 @@ +package view + +import ( + "github.com/infraboard/mcube/v2/ioc/config/validator" + "github.com/infraboard/modules/iam/apps" +) + +func NewMenu() *Menu { + return &Menu{ + ResourceMeta: *apps.NewResourceMeta(), + Pages: []*Page{}, + } +} + +type Menu struct { + // 基础数据 + apps.ResourceMeta + // 菜单定义 + CreateMenuRequest + // 用户是否有权限访问该菜单, 只有在策略模块查询时,才会计算出该字段 + HasPermission *bool `json:"has_permission,omitempty" gorm:"column:has_permission;type:tinyint(1)" optional:"true" description:"用户是否有权限访问该菜单"` + // 菜单关联的页面 + Pages []*Page `json:"pages,omitempty" gorm:"-" description:"菜单关联的页面"` +} + +func (m *Menu) SetHasPermission(v bool) *Menu { + m.HasPermission = &v + return m +} + +func (m *Menu) TableName() string { + return "menus" +} + +func NewCreateMenuRequest() *CreateMenuRequest { + return &CreateMenuRequest{ + Extras: map[string]string{}, + } +} + +type CreateMenuRequest struct { + // 服务 + Service string `json:"service" gorm:"column:service;type:varchar(100);index" bson:"service" description:"服务名称"` + // 父Menu Id + ParentId uint64 `json:"parent_id" bson:"parent_id" gorm:"column:parent_id;type:uint;index" description:"父Menu Id" optional:"true"` + // 菜单路径 + Path string `json:"path" bson:"path" gorm:"column:path" description:"菜单路径" unique:"true"` + // 菜单名称 + Name string `json:"name" bson:"name" gorm:"column:name" description:"菜单名称"` + // 图标 + Icon string `json:"icon" bson:"icon" gorm:"column:icon" description:"图标" optional:"true"` + // 标签 + Label string `json:"label" gorm:"column:label;type:varchar(200);index" description:"标签" optional:"true"` + // 其他扩展信息 + Extras map[string]string `json:"extras" gorm:"column:extras;serializer:json;type:json" description:"其他扩展信息" optional:"true"` +} + +func (r *CreateMenuRequest) Validate() error { + return validator.Validate(r) +} diff --git a/devcloud/mcenter/apps/view/page.go b/devcloud/mcenter/apps/view/page.go new file mode 100644 index 0000000..7262e3c --- /dev/null +++ b/devcloud/mcenter/apps/view/page.go @@ -0,0 +1,64 @@ +package view + +import ( + "github.com/infraboard/mcube/v2/ioc/config/validator" + "github.com/infraboard/modules/iam/apps" +) + +func NewPage() *Page { + return &Page{ + ResourceMeta: *apps.NewResourceMeta(), + } +} + +type Page struct { + // 基础数据 + apps.ResourceMeta + // 菜单定义 + CreatePageRequest + // 用户是否有权限访问该页面, 只有在策略模块查询时,才会计算出该字段 + HasPermission *bool `json:"has_permission,omitempty" gorm:"column:has_permission;type:tinyint(1)" optional:"true" description:"用户是否有权限访问该页面"` +} + +func (p *Page) TableName() string { + return "pages" +} + +func NewCreatePageRequest() *CreatePageRequest { + return &CreatePageRequest{ + Extras: map[string]string{}, + } +} + +type CreatePageRequest struct { + // 菜单Id + MenuId uint64 `json:"menu_id" bson:"menu_id" gorm:"column:menu_id;type:uint;index" description:"菜单Id"` + // 页面路径 + Path string `json:"path" bson:"path" gorm:"column:path" description:"页面路径" unique:"true"` + // 页面名称 + Name string `json:"name" bson:"name" gorm:"column:name" description:"页面名称"` + // 标签 + Label string `json:"label" gorm:"column:label;type:varchar(200);index" description:"标签" optional:"true"` + // 页面组件,比如按钮 + Components []Component `json:"components" gorm:"column:components;type:json;serializer:json" description:"组件" optional:"true"` + // 其他扩展信息 + Extras map[string]string `json:"extras" gorm:"column:extras;serializer:json;type:json" description:"其他扩展信息" optional:"true"` +} + +func (r *CreatePageRequest) Validate() error { + return validator.Validate(r) +} + +// 组件 +type Component struct { + // 组件名称 + Name string `json:"name" bson:"name" description:"组件名称"` + // 组件说明 + Description string `json:"description" optional:"true" description:"组件说明"` + // 组件使用文档链接 + RefDocURL string `json:"ref_doc_url" optional:"true" description:"组件使用文档链接"` + // 关联的Api接口 + RefEndpointId []uint64 `json:"ref_endpoints" description:"该页面管理的Api接口关联的接口" optional:"true"` + // 其他扩展信息 + Extras map[string]string `json:"extras" description:"其他扩展信息" optional:"true"` +} diff --git a/devcloud/mcenter/design.drawio b/devcloud/mcenter/design.drawio index 72c63ea..10767bd 100644 --- a/devcloud/mcenter/design.drawio +++ b/devcloud/mcenter/design.drawio @@ -1,6 +1,6 @@ - + @@ -127,7 +127,7 @@ - + @@ -139,19 +139,19 @@ - + - + - + - + - + diff --git a/devcloud/mcenter/test/set_up.go b/devcloud/mcenter/test/set_up.go index 6337f83..343d51a 100644 --- a/devcloud/mcenter/test/set_up.go +++ b/devcloud/mcenter/test/set_up.go @@ -10,7 +10,7 @@ import ( _ "122.51.31.227/go-course/go18/devcloud/mcenter/apps" ) -func DevelopmentSet() { +func DevelopmentSetUp() { // import 后自动执行的逻辑 // 工具对象的初始化, 需要的是绝对路径 ioc.DevelopmentSetupWithPath(os.Getenv("CONFIG_PATH")) diff --git a/go.mod b/go.mod index e7ed942..76699f3 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/emicklei/go-restful-openapi/v2 v2.11.0 github.com/emicklei/go-restful/v3 v3.12.2 github.com/gin-gonic/gin v1.10.0 + github.com/google/uuid v1.6.0 github.com/infraboard/mcube/v2 v2.0.59 github.com/infraboard/modules v0.0.12 github.com/rs/zerolog v1.34.0 @@ -42,7 +43,6 @@ require ( github.com/go-playground/validator/v10 v10.26.0 // indirect github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/goccy/go-json v0.10.5 // indirect - github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect diff --git a/homework/README.md b/homework/README.md index 716658d..a53938c 100644 --- a/homework/README.md +++ b/homework/README.md @@ -15,7 +15,9 @@ 项目名称: devcloud + [GO18015-鑫](https://github.com/sword-demon/go18) -+ [GO18012-Leowy](https://gitee.com/leo_wy/GoProjects.git) + [GO18005-康](https://github.com/YouthInThinking/GoProject.git) + [GO18007-磊](https://gitee.com/wangleisir/mage_go_project.git) -+ [硕](https://gitee.com/liushuowalk/kubernetersgo) \ No newline at end of file + ++ [GO18012-Leowy](https://gitee.com/leo_wy/GoProjects.git) ++ [硕](https://gitee.com/liushuowalk/kubernetersgo) +