update role

This commit is contained in:
yumaojun03 2025-06-08 15:11:14 +08:00
parent d10c26eb3b
commit 9eb977cfbf
24 changed files with 1109 additions and 15 deletions

View File

@ -3,7 +3,9 @@ package endpoint
type ACCESS_MODE uint8
const (
// 只读模式
ACCESS_MODE_READ = iota
// 读写模式
ACCESS_MODE_READ_WRITE
)

View File

@ -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
}

View File

@ -0,0 +1,19 @@
<mxfile host="65bd71144e">
<diagram id="prDIldifm9lRc8bqSxY8" name="第 1 页">
<mxGraphModel dx="892" dy="476" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="2" value="Role" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="210" y="230" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="3" value="Api Permission" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="390" y="120" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="4" value="Api Permission" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="390" y="340" width="120" height="60" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

View File

@ -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
)

View File

@ -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
}

View File

@ -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)
}

View File

@ -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()
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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"`
}

View File

@ -13,6 +13,6 @@ var (
)
func init() {
test.DevelopmentSet()
test.DevelopmentSetUp()
svc = token.GetService()
}

View File

@ -18,5 +18,5 @@ func TestPasswordIssuer(t *testing.T) {
}
func init() {
test.DevelopmentSet()
test.DevelopmentSetUp()
}

View File

@ -18,5 +18,5 @@ func TestPasswordIssuer(t *testing.T) {
}
func init() {
test.DevelopmentSet()
test.DevelopmentSetUp()
}

View File

@ -13,6 +13,6 @@ var (
)
func init() {
test.DevelopmentSet()
test.DevelopmentSetUp()
impl = user.GetService()
}

View File

@ -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)
}

View File

@ -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"`
}

View File

@ -1,6 +1,6 @@
<mxfile host="65bd71144e">
<diagram id="kBUSl4Twz2xUubsumQ87" name="第 1 页">
<mxGraphModel dx="892" dy="554" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<mxGraphModel dx="892" dy="371" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
@ -127,7 +127,7 @@
<mxCell id="38" value="权限拦截器" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="120" y="830" width="120" height="45" as="geometry"/>
</mxCell>
<mxCell id="40" value="授权策略&lt;div&gt;用户的授权策略&lt;/div&gt;&lt;div&gt;User, Name, 接口&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxCell id="40" value="授权策略&lt;div&gt;用户的授权策略&lt;/div&gt;&lt;div&gt;User, 空间, Role&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="294" y="830" width="120" height="50" as="geometry"/>
</mxCell>
<mxCell id="42" value="空间&lt;div&gt;Namepsace&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
@ -139,19 +139,19 @@
<mxCell id="44" value="接口列表" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="760" y="825" width="120" height="55" as="geometry"/>
</mxCell>
<mxCell id="45" value="" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxCell id="45" value="" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="100" y="1090" width="330" height="180" as="geometry"/>
</mxCell>
<mxCell id="46" value="用户认证" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxCell id="46" value="用户认证" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="100" y="1060" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="49" style="edgeStyle=none;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;" edge="1" parent="1" source="47" target="48">
<mxCell id="49" style="edgeStyle=none;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="47" target="48" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="47" value="token" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxCell id="47" value="token" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="280" y="1119" width="120" height="50" as="geometry"/>
</mxCell>
<mxCell id="48" value="user" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxCell id="48" value="user" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="120" y="1119" width="120" height="50" as="geometry"/>
</mxCell>
</root>

View File

@ -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"))

2
go.mod
View File

@ -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

View File

@ -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)
+ [GO18012-Leowy](https://gitee.com/leo_wy/GoProjects.git)
+ [](https://gitee.com/liushuowalk/kubernetersgo)