Compare commits
No commits in common. "main" and "day04.03" have entirely different histories.
@ -1,7 +1,7 @@
|
|||||||
[app]
|
[app]
|
||||||
name = "devcloud"
|
name = "devcloud"
|
||||||
description = "app desc"
|
description = "app desc"
|
||||||
address = "http://127.0.0.1:8080"
|
address = "localhost"
|
||||||
encrypt_key = "defualt app encrypt key"
|
encrypt_key = "defualt app encrypt key"
|
||||||
|
|
||||||
[datasource]
|
[datasource]
|
||||||
|
@ -1,17 +1,2 @@
|
|||||||
# 接口管理
|
# 接口管理
|
||||||
|
|
||||||
如何提取 当前这个服务的路由条目, GoRestful框架的Container这一层 获取
|
|
||||||
|
|
||||||
```go
|
|
||||||
func NewEntryFromRestfulContainer(c *restful.Container) (entries []*RouteEntry) {
|
|
||||||
wss := c.RegisteredWebServices()
|
|
||||||
for i := range wss {
|
|
||||||
for _, route := range wss[i].Routes() {
|
|
||||||
es := NewEntryFromRestRoute(route)
|
|
||||||
entries = append(entries, es)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return entries
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
package endpoint
|
|
||||||
|
|
||||||
type ACCESS_MODE uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
// 只读模式
|
|
||||||
ACCESS_MODE_READ = iota
|
|
||||||
// 读写模式
|
|
||||||
ACCESS_MODE_READ_WRITE
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
META_REQUIRED_AUTH_KEY = "required_auth"
|
|
||||||
META_REQUIRED_CODE_KEY = "required_code"
|
|
||||||
META_REQUIRED_PERM_KEY = "required_perm"
|
|
||||||
META_REQUIRED_ROLE_KEY = "required_role"
|
|
||||||
META_REQUIRED_AUDIT_KEY = "required_audit"
|
|
||||||
META_REQUIRED_NAMESPACE_KEY = "required_namespace"
|
|
||||||
META_RESOURCE_KEY = "resource"
|
|
||||||
META_ACTION_KEY = "action"
|
|
||||||
)
|
|
@ -1,92 +0,0 @@
|
|||||||
package impl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/endpoint"
|
|
||||||
"github.com/infraboard/mcube/v2/exception"
|
|
||||||
"github.com/infraboard/mcube/v2/ioc/config/datasource"
|
|
||||||
"github.com/infraboard/mcube/v2/types"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 注册API接口
|
|
||||||
// 这是一个批量接口, 一次添加多条记录
|
|
||||||
// 需要保证事务: 同时成功,或者同时失败, MySQL事务
|
|
||||||
func (i *EndpointServiceImpl) RegistryEndpoint(ctx context.Context, in *endpoint.RegistryEndpointRequest) (*types.Set[*endpoint.Endpoint], error) {
|
|
||||||
if err := in.Validate(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
set := types.New[*endpoint.Endpoint]()
|
|
||||||
err := datasource.DBFromCtx(ctx).Transaction(func(tx *gorm.DB) error {
|
|
||||||
for i := range in.Items {
|
|
||||||
item := in.Items[i].BuildUUID()
|
|
||||||
ins := endpoint.NewEndpoint().SetRouteEntry(*item)
|
|
||||||
|
|
||||||
oldEnpoint := endpoint.NewEndpoint()
|
|
||||||
if err := tx.Where("uuid = ?", item.UUID).Take(oldEnpoint).Error; err != nil {
|
|
||||||
if err != gorm.ErrRecordNotFound {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 需要创建
|
|
||||||
if err := tx.Save(ins).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 需要更新
|
|
||||||
ins.Id = oldEnpoint.Id
|
|
||||||
if err := tx.Where("uuid = ?", item.UUID).Updates(ins).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
set.Add(ins)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return set, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询API接口列表
|
|
||||||
func (i *EndpointServiceImpl) QueryEndpoint(ctx context.Context, in *endpoint.QueryEndpointRequest) (*types.Set[*endpoint.Endpoint], error) {
|
|
||||||
set := types.New[*endpoint.Endpoint]()
|
|
||||||
|
|
||||||
query := datasource.DBFromCtx(ctx).Model(&endpoint.Endpoint{})
|
|
||||||
if len(in.Services) > 0 && !in.IsMatchAllService() {
|
|
||||||
query = query.Where("service IN ?", in.Services)
|
|
||||||
}
|
|
||||||
|
|
||||||
err := query.Count(&set.Total).Error
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = query.
|
|
||||||
Order("created_at desc").
|
|
||||||
Find(&set.Items).
|
|
||||||
Error
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return set, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询API接口详情
|
|
||||||
func (i *EndpointServiceImpl) DescribeEndpoint(ctx context.Context, in *endpoint.DescribeEndpointRequest) (*endpoint.Endpoint, error) {
|
|
||||||
query := datasource.DBFromCtx(ctx)
|
|
||||||
|
|
||||||
ins := &endpoint.Endpoint{}
|
|
||||||
if err := query.First(ins).Error; err != nil {
|
|
||||||
if err == gorm.ErrRecordNotFound {
|
|
||||||
return nil, exception.NewNotFound("endpoint %d not found", in.Id)
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return ins, nil
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
package impl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/endpoint"
|
|
||||||
"github.com/infraboard/mcube/v2/ioc"
|
|
||||||
"github.com/infraboard/mcube/v2/ioc/config/datasource"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
ioc.Controller().Registry(&EndpointServiceImpl{})
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ endpoint.Service = (*EndpointServiceImpl)(nil)
|
|
||||||
|
|
||||||
// 他是user service 服务的控制器
|
|
||||||
type EndpointServiceImpl struct {
|
|
||||||
ioc.ObjectImpl
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *EndpointServiceImpl) Init() error {
|
|
||||||
// 自动创建表
|
|
||||||
if datasource.Get().AutoMigrate {
|
|
||||||
err := datasource.DB().AutoMigrate(&endpoint.Endpoint{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 定义托管到Ioc里面的名称
|
|
||||||
func (i *EndpointServiceImpl) Name() string {
|
|
||||||
return endpoint.APP_NAME
|
|
||||||
}
|
|
@ -1,77 +0,0 @@
|
|||||||
package endpoint
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"slices"
|
|
||||||
|
|
||||||
"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 (
|
|
||||||
APP_NAME = "endpoint"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetService() Service {
|
|
||||||
return ioc.Controller().Get(APP_NAME).(Service)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Service interface {
|
|
||||||
// 注册API接口
|
|
||||||
RegistryEndpoint(context.Context, *RegistryEndpointRequest) (*types.Set[*Endpoint], error)
|
|
||||||
// 查询API接口列表
|
|
||||||
QueryEndpoint(context.Context, *QueryEndpointRequest) (*types.Set[*Endpoint], error)
|
|
||||||
// 查询API接口详情
|
|
||||||
DescribeEndpoint(context.Context, *DescribeEndpointRequest) (*Endpoint, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewQueryEndpointRequest() *QueryEndpointRequest {
|
|
||||||
return &QueryEndpointRequest{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type QueryEndpointRequest struct {
|
|
||||||
Services []string `form:"services" json:"serivces"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *QueryEndpointRequest) WithService(services ...string) *QueryEndpointRequest {
|
|
||||||
for _, service := range services {
|
|
||||||
if !slices.Contains(r.Services, service) {
|
|
||||||
r.Services = append(r.Services, services...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *QueryEndpointRequest) IsMatchAllService() bool {
|
|
||||||
return slices.Contains(r.Services, "*")
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDescribeEndpointRequest() *DescribeEndpointRequest {
|
|
||||||
return &DescribeEndpointRequest{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type DescribeEndpointRequest struct {
|
|
||||||
apps.GetRequest
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRegistryEndpointRequest() *RegistryEndpointRequest {
|
|
||||||
return &RegistryEndpointRequest{
|
|
||||||
Items: []*RouteEntry{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type RegistryEndpointRequest struct {
|
|
||||||
Items []*RouteEntry `json:"items"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RegistryEndpointRequest) AddItem(items ...*RouteEntry) *RegistryEndpointRequest {
|
|
||||||
r.Items = append(r.Items, items...)
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RegistryEndpointRequest) Validate() error {
|
|
||||||
return validator.Validate(r)
|
|
||||||
}
|
|
@ -1,251 +0,0 @@
|
|||||||
package endpoint
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/emicklei/go-restful/v3"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/infraboard/mcube/v2/ioc/config/application"
|
|
||||||
"github.com/infraboard/mcube/v2/tools/pretty"
|
|
||||||
"github.com/infraboard/mcube/v2/types"
|
|
||||||
"github.com/infraboard/modules/iam/apps"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewEndpoint() *Endpoint {
|
|
||||||
return &Endpoint{
|
|
||||||
ResourceMeta: *apps.NewResourceMeta(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsEndpointExist(set *types.Set[*Endpoint], target *Endpoint) bool {
|
|
||||||
for _, item := range set.Items {
|
|
||||||
if item.Id == target.Id {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Endpoint Service's features
|
|
||||||
type Endpoint struct {
|
|
||||||
// 基础数据
|
|
||||||
apps.ResourceMeta
|
|
||||||
// 路由条目信息
|
|
||||||
RouteEntry `bson:",inline" validate:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Endpoint) TableName() string {
|
|
||||||
return "endpoints"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Endpoint) String() string {
|
|
||||||
return pretty.ToJSON(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Endpoint) IsMatched(service, method, path string) bool {
|
|
||||||
if e.Service != service {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if e.Method != method {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if e.Path != path {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *Endpoint) SetRouteEntry(v RouteEntry) *Endpoint {
|
|
||||||
u.RouteEntry = v
|
|
||||||
return u
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRouteEntry() *RouteEntry {
|
|
||||||
return &RouteEntry{
|
|
||||||
RequiredRole: []string{},
|
|
||||||
Extras: map[string]string{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Entry 路由条目, service-method-path
|
|
||||||
type RouteEntry struct {
|
|
||||||
// 该功能属于那个服务
|
|
||||||
UUID string `json:"uuid" bson:"uuid" gorm:"column:uuid;type:varchar(100);uniqueIndex" optional:"true" description:"路由UUID"`
|
|
||||||
// 该功能属于那个服务
|
|
||||||
Service string `json:"service" bson:"service" validate:"required,lte=64" gorm:"column:service;type:varchar(100);index" description:"服务名称"`
|
|
||||||
// 服务那个版本的功能
|
|
||||||
Version string `json:"version" bson:"version" validate:"required,lte=64" gorm:"column:version;type:varchar(100)" optional:"true" description:"版本版本"`
|
|
||||||
// 资源名称
|
|
||||||
Resource string `json:"resource" bson:"resource" gorm:"column:resource;type:varchar(100);index" description:"资源名称"`
|
|
||||||
// 资源操作
|
|
||||||
Action string `json:"action" bson:"action" gorm:"column:action;type:varchar(100);index" description:"资源操作"`
|
|
||||||
// 读或者写
|
|
||||||
AccessMode ACCESS_MODE `json:"access_mode" bson:"access_mode" gorm:"column:access_mode;type:tinyint(1);index" optional:"true" description:"读写权限"`
|
|
||||||
// 操作标签
|
|
||||||
ActionLabel string `json:"action_label" gorm:"column:action_label;type:varchar(200);index" optional:"true" description:"资源标签"`
|
|
||||||
// 函数名称
|
|
||||||
FunctionName string `json:"function_name" bson:"function_name" gorm:"column:function_name;type:varchar(100)" optional:"true" description:"函数名称"`
|
|
||||||
// HTTP path 用于自动生成http api
|
|
||||||
Path string `json:"path" bson:"path" gorm:"column:path;type:varchar(200);index" description:"接口的路径"`
|
|
||||||
// HTTP method 用于自动生成http api
|
|
||||||
Method string `json:"method" bson:"method" gorm:"column:method;type:varchar(100);index" description:"接口的方法"`
|
|
||||||
// 接口说明
|
|
||||||
Description string `json:"description" bson:"description" gorm:"column:description;type:text" optional:"true" description:"接口说明"`
|
|
||||||
// 是否校验用户身份 (acccess_token 校验)
|
|
||||||
RequiredAuth bool `json:"required_auth" bson:"required_auth" gorm:"column:required_auth;type:tinyint(1)" optional:"true" description:"是否校验用户身份 (acccess_token 校验)"`
|
|
||||||
// 验证码校验(开启双因子认证需要) (code 校验)
|
|
||||||
RequiredCode bool `json:"required_code" bson:"required_code" gorm:"column:required_code;type:tinyint(1)" optional:"true" description:"验证码校验(开启双因子认证需要) (code 校验)"`
|
|
||||||
// 开启鉴权
|
|
||||||
RequiredPerm bool `json:"required_perm" bson:"required_perm" gorm:"column:required_perm;type:tinyint(1)" optional:"true" description:"开启鉴权"`
|
|
||||||
// ACL模式下, 允许的通过的身份标识符, 比如角色, 用户类型之类
|
|
||||||
RequiredRole []string `json:"required_role" bson:"required_role" gorm:"column:required_role;serializer:json;type:json" optional:"true" description:"ACL模式下, 允许的通过的身份标识符, 比如角色, 用户类型之类"`
|
|
||||||
// 是否开启操作审计, 开启后这次操作将被记录
|
|
||||||
RequiredAudit bool `json:"required_audit" bson:"required_audit" gorm:"column:required_audit;type:tinyint(1)" optional:"true" description:"是否开启操作审计, 开启后这次操作将被记录"`
|
|
||||||
// 名称空间不能为空
|
|
||||||
RequiredNamespace bool `json:"required_namespace" bson:"required_namespace" gorm:"column:required_namespace;type:tinyint(1)" optional:"true" description:"名称空间不能为空"`
|
|
||||||
// 扩展信息
|
|
||||||
Extras map[string]string `json:"extras" bson:"extras" gorm:"column:extras;serializer:json;type:json" optional:"true" description:"扩展信息"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// service-method-path
|
|
||||||
func (e *RouteEntry) BuildUUID() *RouteEntry {
|
|
||||||
e.UUID = uuid.NewSHA1(uuid.Nil, fmt.Appendf(nil, "%s-%s-%s", e.Service, e.Method, e.Path)).String()
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetRouteMeta[T any](m map[string]any, key string) T {
|
|
||||||
if v, ok := m[key]; ok {
|
|
||||||
return v.(T)
|
|
||||||
}
|
|
||||||
|
|
||||||
var t T
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
// func GetRouteMetaString(m map[string]any, key string) string {
|
|
||||||
// if v, ok := m[key]; ok {
|
|
||||||
// return v.(string)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// var t string
|
|
||||||
// return t
|
|
||||||
// }
|
|
||||||
|
|
||||||
func (e *RouteEntry) LoadMeta(meta map[string]any) {
|
|
||||||
e.Service = application.Get().AppName
|
|
||||||
e.Resource = GetRouteMeta[string](meta, META_RESOURCE_KEY)
|
|
||||||
e.Action = GetRouteMeta[string](meta, META_ACTION_KEY)
|
|
||||||
e.RequiredAuth = GetRouteMeta[bool](meta, META_REQUIRED_AUTH_KEY)
|
|
||||||
e.RequiredCode = GetRouteMeta[bool](meta, META_REQUIRED_CODE_KEY)
|
|
||||||
e.RequiredPerm = GetRouteMeta[bool](meta, META_REQUIRED_PERM_KEY)
|
|
||||||
e.RequiredRole = GetRouteMeta[[]string](meta, META_REQUIRED_ROLE_KEY)
|
|
||||||
e.RequiredAudit = GetRouteMeta[bool](meta, META_REQUIRED_AUDIT_KEY)
|
|
||||||
e.RequiredNamespace = GetRouteMeta[bool](meta, META_REQUIRED_NAMESPACE_KEY)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UniquePath todo
|
|
||||||
func (e *RouteEntry) HasRequiredRole() bool {
|
|
||||||
return len(e.RequiredRole) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// UniquePath todo
|
|
||||||
func (e *RouteEntry) UniquePath() string {
|
|
||||||
return fmt.Sprintf("%s.%s", e.Method, e.Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *RouteEntry) IsRequireRole(target string) bool {
|
|
||||||
for i := range e.RequiredRole {
|
|
||||||
if e.RequiredRole[i] == "*" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.RequiredRole[i] == target {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *RouteEntry) SetRequiredAuth(v bool) *RouteEntry {
|
|
||||||
e.RequiredAuth = v
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *RouteEntry) AddRequiredRole(roles ...string) *RouteEntry {
|
|
||||||
e.RequiredRole = append(e.RequiredRole, roles...)
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *RouteEntry) SetRequiredPerm(v bool) *RouteEntry {
|
|
||||||
e.RequiredPerm = v
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *RouteEntry) SetLabel(value string) *RouteEntry {
|
|
||||||
e.ActionLabel = value
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *RouteEntry) SetExtensionFromMap(m map[string]string) *RouteEntry {
|
|
||||||
if e.Extras == nil {
|
|
||||||
e.Extras = map[string]string{}
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range m {
|
|
||||||
e.Extras[k] = v
|
|
||||||
}
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *RouteEntry) SetRequiredCode(v bool) *RouteEntry {
|
|
||||||
e.RequiredCode = v
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewEntryFromRestRequest(req *restful.Request) *RouteEntry {
|
|
||||||
entry := NewRouteEntry()
|
|
||||||
|
|
||||||
// 请求拦截
|
|
||||||
route := req.SelectedRoute()
|
|
||||||
if route == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
entry.FunctionName = route.Operation()
|
|
||||||
entry.Method = route.Method()
|
|
||||||
entry.LoadMeta(route.Metadata())
|
|
||||||
entry.Path = route.Path()
|
|
||||||
return entry
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewEntryFromRestRouteReader(route restful.RouteReader) *RouteEntry {
|
|
||||||
entry := NewRouteEntry()
|
|
||||||
entry.FunctionName = route.Operation()
|
|
||||||
entry.Method = route.Method()
|
|
||||||
entry.LoadMeta(route.Metadata())
|
|
||||||
entry.Path = route.Path()
|
|
||||||
return entry
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewEntryFromRestRoute(route restful.Route) *RouteEntry {
|
|
||||||
entry := NewRouteEntry()
|
|
||||||
entry.FunctionName = route.Operation
|
|
||||||
entry.Method = route.Method
|
|
||||||
entry.LoadMeta(route.Metadata)
|
|
||||||
entry.Path = route.Path
|
|
||||||
return entry
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewEntryFromRestfulContainer(c *restful.Container) (entries []*RouteEntry) {
|
|
||||||
// 获取当前Container里面所有的 WebService
|
|
||||||
wss := c.RegisteredWebServices()
|
|
||||||
for i := range wss {
|
|
||||||
// 获取WebService下的路由条目
|
|
||||||
for _, route := range wss[i].Routes() {
|
|
||||||
es := NewEntryFromRestRoute(route)
|
|
||||||
entries = append(entries, es)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return entries
|
|
||||||
}
|
|
@ -7,10 +7,6 @@ import (
|
|||||||
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps/token/api"
|
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps/token/api"
|
||||||
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps/token/impl"
|
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps/token/impl"
|
||||||
|
|
||||||
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps/endpoint/impl"
|
|
||||||
|
|
||||||
// 颁发器
|
// 颁发器
|
||||||
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps/token/issuers"
|
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps/token/issuers"
|
||||||
// 鉴权中间件
|
|
||||||
_ "122.51.31.227/go-course/go18/devcloud/mcenter/permission"
|
|
||||||
)
|
)
|
||||||
|
@ -1,102 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
<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>
|
|
@ -1,18 +0,0 @@
|
|||||||
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
|
|
||||||
)
|
|
@ -1,110 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -1,103 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -1,59 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
@ -1,82 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -1,265 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
@ -1,66 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
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"`
|
|
||||||
}
|
|
@ -33,28 +33,3 @@ type Service interface {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## 接口的实现
|
|
||||||
|
|
||||||
```go
|
|
||||||
func TestIssueToken(t *testing.T) {
|
|
||||||
req := token.NewIssueTokenRequest()
|
|
||||||
req.IssueByPassword("admin", "123456")
|
|
||||||
req.Source = token.SOURCE_WEB
|
|
||||||
set, err := svc.IssueToken(ctx, req)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
t.Log(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestQueryToken(t *testing.T) {
|
|
||||||
req := token.NewQueryTokenRequest()
|
|
||||||
set, err := svc.QueryToken(ctx, req)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
t.Log(set)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
@ -1,72 +1 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
|
||||||
_ "embed"
|
|
||||||
|
|
||||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/token"
|
|
||||||
"github.com/infraboard/mcube/v2/ioc"
|
|
||||||
"github.com/infraboard/mcube/v2/ioc/config/gorestful"
|
|
||||||
|
|
||||||
restfulspec "github.com/emicklei/go-restful-openapi/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
ioc.Api().Registry(&TokenRestfulApiHandler{})
|
|
||||||
}
|
|
||||||
|
|
||||||
type TokenRestfulApiHandler struct {
|
|
||||||
ioc.ObjectImpl
|
|
||||||
|
|
||||||
// 依赖控制器
|
|
||||||
svc token.Service
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *TokenRestfulApiHandler) Name() string {
|
|
||||||
return token.APP_NAME
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:embed docs/login.md
|
|
||||||
var loginApiDocNotes string
|
|
||||||
|
|
||||||
func (h *TokenRestfulApiHandler) Init() error {
|
|
||||||
h.svc = token.GetService()
|
|
||||||
|
|
||||||
tags := []string{"登录管理"}
|
|
||||||
ws := gorestful.ObjectRouter(h)
|
|
||||||
ws.Route(ws.POST("").To(h.Login).
|
|
||||||
Doc("颁发令牌(登录)").
|
|
||||||
Notes(loginApiDocNotes).
|
|
||||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
|
||||||
Reads(token.IssueTokenRequest{}).
|
|
||||||
Writes(token.Token{}).
|
|
||||||
Returns(200, "OK", token.Token{}))
|
|
||||||
|
|
||||||
ws.Route(ws.POST("/validate").To(h.ValiateToken).
|
|
||||||
Doc("校验令牌").
|
|
||||||
// Metadata(permission.Auth(true)).
|
|
||||||
// Metadata(permission.Permission(false)).
|
|
||||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
|
||||||
Reads(token.ValiateTokenRequest{}).
|
|
||||||
Writes(token.Token{}).
|
|
||||||
Returns(200, "OK", token.Token{}))
|
|
||||||
|
|
||||||
// ws.Route(ws.POST("/change_namespace").To(h.ChangeNamespce).
|
|
||||||
// Doc("切换令牌访问空间").
|
|
||||||
// // Metadata(permission.Auth(true)).
|
|
||||||
// // Metadata(permission.Permission(false)).
|
|
||||||
// Metadata(restfulspec.KeyOpenAPITags, tags).
|
|
||||||
// Reads(token.ChangeNamespceRequest{}).
|
|
||||||
// Writes(token.Token{}).
|
|
||||||
// Returns(200, "OK", token.Token{}))
|
|
||||||
|
|
||||||
ws.Route(ws.DELETE("").To(h.Logout).
|
|
||||||
Doc("撤销令牌(退出)").
|
|
||||||
// Metadata(permission.Auth(true)).
|
|
||||||
// Metadata(permission.Permission(false)).
|
|
||||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
|
||||||
Reads(token.IssueTokenRequest{}).
|
|
||||||
Writes(token.Token{}).
|
|
||||||
Returns(200, "OK", token.Token{}).
|
|
||||||
Returns(404, "Not Found", nil))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
登录接口
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"username": "admin",
|
|
||||||
"password": "123456"
|
|
||||||
}
|
|
||||||
```
|
|
@ -1,127 +1 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
|
|
||||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/token"
|
|
||||||
"github.com/emicklei/go-restful/v3"
|
|
||||||
"github.com/infraboard/mcube/v2/http/restful/response"
|
|
||||||
"github.com/infraboard/mcube/v2/ioc/config/application"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (h *TokenRestfulApiHandler) Login(r *restful.Request, w *restful.Response) {
|
|
||||||
// 1. 获取用户的请求参数, 参数在Body里面
|
|
||||||
req := token.NewIssueTokenRequest()
|
|
||||||
|
|
||||||
// 获取用户通过body传入的参数
|
|
||||||
err := r.ReadEntity(req)
|
|
||||||
if err != nil {
|
|
||||||
response.Failed(w, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置当前调用者的Token
|
|
||||||
// Private 用户自己的Token
|
|
||||||
// 如果你是user/password 这种方式,token 直接放到body
|
|
||||||
switch req.Issuer {
|
|
||||||
case token.ISSUER_PRIVATE_TOKEN:
|
|
||||||
req.Parameter.SetAccessToken(token.GetAccessTokenFromHTTP(r.Request))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 执行逻辑
|
|
||||||
tk, err := h.svc.IssueToken(r.Request.Context(), req)
|
|
||||||
if err != nil {
|
|
||||||
response.Failed(w, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// access_token 通过SetCookie 直接写到浏览器客户端(Web)
|
|
||||||
http.SetCookie(w, &http.Cookie{
|
|
||||||
Name: token.ACCESS_TOKEN_COOKIE_NAME,
|
|
||||||
Value: url.QueryEscape(tk.AccessToken),
|
|
||||||
MaxAge: 0,
|
|
||||||
Path: "/",
|
|
||||||
Domain: application.Get().Domain(),
|
|
||||||
SameSite: http.SameSiteDefaultMode,
|
|
||||||
Secure: false,
|
|
||||||
HttpOnly: true,
|
|
||||||
})
|
|
||||||
// 在Header头中也添加Token
|
|
||||||
w.Header().Set(token.ACCESS_TOKEN_RESPONSE_HEADER_NAME, tk.AccessToken)
|
|
||||||
|
|
||||||
// 3. Body中返回Token对象
|
|
||||||
response.Success(w, tk)
|
|
||||||
}
|
|
||||||
|
|
||||||
// func (h *TokenRestulApiHandler) ChangeNamespce(r *restful.Request, w *restful.Response) {
|
|
||||||
// // 1. 获取用户的请求参数, 参数在Body里面
|
|
||||||
// req := token.NewChangeNamespceRequest()
|
|
||||||
// err := r.ReadEntity(req)
|
|
||||||
// if err != nil {
|
|
||||||
// response.Failed(w, err)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// tk := token.GetTokenFromCtx(r.Request.Context())
|
|
||||||
// req.UserId = tk.UserId
|
|
||||||
|
|
||||||
// // 2. 执行逻辑
|
|
||||||
// tk, err = h.svc.ChangeNamespce(r.Request.Context(), req)
|
|
||||||
// if err != nil {
|
|
||||||
// response.Failed(w, err)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // 3. Body中返回Token对象
|
|
||||||
// response.Success(w, tk)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Logout HandleFunc
|
|
||||||
func (h *TokenRestfulApiHandler) Logout(r *restful.Request, w *restful.Response) {
|
|
||||||
req := token.NewRevolkTokenRequest(
|
|
||||||
token.GetAccessTokenFromHTTP(r.Request),
|
|
||||||
token.GetRefreshTokenFromHTTP(r.Request),
|
|
||||||
)
|
|
||||||
|
|
||||||
tk, err := h.svc.RevolkToken(r.Request.Context(), req)
|
|
||||||
if err != nil {
|
|
||||||
response.Failed(w, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// access_token 通过SetCookie 直接写到浏览器客户端(Web)
|
|
||||||
http.SetCookie(w, &http.Cookie{
|
|
||||||
Name: token.ACCESS_TOKEN_COOKIE_NAME,
|
|
||||||
Value: "",
|
|
||||||
MaxAge: 0,
|
|
||||||
Path: "/",
|
|
||||||
Domain: application.Get().Domain(),
|
|
||||||
SameSite: http.SameSiteDefaultMode,
|
|
||||||
Secure: false,
|
|
||||||
HttpOnly: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
// 3. 返回响应
|
|
||||||
response.Success(w, tk)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *TokenRestfulApiHandler) ValiateToken(r *restful.Request, w *restful.Response) {
|
|
||||||
// 1. 获取用户的请求参数, 参数在Body里面
|
|
||||||
req := token.NewValiateTokenRequest("")
|
|
||||||
err := r.ReadEntity(req)
|
|
||||||
if err != nil {
|
|
||||||
response.Failed(w, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 执行逻辑
|
|
||||||
tk, err := h.svc.ValiateToken(r.Request.Context(), req)
|
|
||||||
if err != nil {
|
|
||||||
response.Failed(w, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Body中返回Token对象
|
|
||||||
response.Success(w, tk)
|
|
||||||
}
|
|
||||||
|
@ -13,6 +13,6 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
test.DevelopmentSetUp()
|
test.DevelopmentSet()
|
||||||
svc = token.GetService()
|
svc = token.GetService()
|
||||||
}
|
}
|
||||||
|
@ -18,5 +18,5 @@ func TestPasswordIssuer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
test.DevelopmentSetUp()
|
test.DevelopmentSet()
|
||||||
}
|
}
|
||||||
|
@ -18,5 +18,5 @@ func TestPasswordIssuer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
test.DevelopmentSetUp()
|
test.DevelopmentSet()
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,6 @@ func GetAccessTokenFromHTTP(r *http.Request) string {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
// ?token=xxxx
|
|
||||||
tk, _ = url.QueryUnescape(cookie.Value)
|
tk, _ = url.QueryUnescape(cookie.Value)
|
||||||
} else {
|
} else {
|
||||||
// 处理 带格式: Bearer <Your API key>
|
// 处理 带格式: Bearer <Your API key>
|
||||||
@ -34,7 +33,6 @@ func GetAccessTokenFromHTTP(r *http.Request) string {
|
|||||||
return tk
|
return tk
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从上下文中 提取 用户身份信息
|
|
||||||
func GetTokenFromCtx(ctx context.Context) *Token {
|
func GetTokenFromCtx(ctx context.Context) *Token {
|
||||||
if v := ctx.Value(CTX_TOKEN_KEY); v != nil {
|
if v := ctx.Value(CTX_TOKEN_KEY); v != nil {
|
||||||
return v.(*Token)
|
return v.(*Token)
|
||||||
|
@ -65,27 +65,3 @@ func (u *User) CheckPassword(password string) error {
|
|||||||
return bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
|
return bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 接口设计
|
|
||||||
|
|
||||||
```go
|
|
||||||
ws.Route(ws.GET("").To(h.QueryUser).
|
|
||||||
Doc("用户列表查询").
|
|
||||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
|
||||||
Param(restful.QueryParameter("page_size", "分页大小").DataType("integer")).
|
|
||||||
Param(restful.QueryParameter("page_number", "页码").DataType("integer")).
|
|
||||||
Writes(Set{}).
|
|
||||||
Returns(200, "OK", Set{}))
|
|
||||||
```
|
|
||||||
|
|
||||||
其中 user字段里面的
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"password": "$2a$10$GoEjC.vFlgJ..BCvaMu6YurdVgyx4p6S4LFRXiqXESiVY4lokL496"
|
|
||||||
}
|
|
||||||
// 需要脱敏: "*"
|
|
||||||
{
|
|
||||||
"password": "****"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
@ -1,55 +1 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
|
||||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/user"
|
|
||||||
"122.51.31.227/go-course/go18/devcloud/mcenter/permission"
|
|
||||||
"github.com/infraboard/mcube/v2/ioc"
|
|
||||||
"github.com/infraboard/mcube/v2/ioc/config/gorestful"
|
|
||||||
|
|
||||||
restfulspec "github.com/emicklei/go-restful-openapi/v2"
|
|
||||||
"github.com/emicklei/go-restful/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
ioc.Api().Registry(&UserRestfulApiHandler{})
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserRestfulApiHandler struct {
|
|
||||||
ioc.ObjectImpl
|
|
||||||
|
|
||||||
// 依赖控制器
|
|
||||||
svc user.Service
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *UserRestfulApiHandler) Name() string {
|
|
||||||
return "users"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *UserRestfulApiHandler) Init() error {
|
|
||||||
h.svc = user.GetService()
|
|
||||||
|
|
||||||
tags := []string{"用户登录"}
|
|
||||||
ws := gorestful.ObjectRouter(h)
|
|
||||||
// required_auth=true/false
|
|
||||||
ws.Route(ws.GET("").To(h.QueryUser).
|
|
||||||
Doc("用户列表查询").
|
|
||||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
|
||||||
// 这个开关怎么生效
|
|
||||||
// 中间件需求读取接口的描述信息,来决定是否需要认证
|
|
||||||
Metadata(permission.Auth(true)).
|
|
||||||
Metadata(permission.Resource("user")).
|
|
||||||
Metadata(permission.Action("list")).
|
|
||||||
Param(restful.QueryParameter("page_size", "分页大小").DataType("integer")).
|
|
||||||
Param(restful.QueryParameter("page_number", "页码").DataType("integer")).
|
|
||||||
Writes(Set{}).
|
|
||||||
Returns(200, "OK", Set{}))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// *types.Set[*User]
|
|
||||||
// 返回的泛型, API Doc这个工具 不支持泛型
|
|
||||||
type Set struct {
|
|
||||||
Total int64 `json:"total"`
|
|
||||||
Items []user.User `json:"items"`
|
|
||||||
}
|
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/user"
|
|
||||||
"github.com/emicklei/go-restful/v3"
|
|
||||||
"github.com/gin-gonic/gin/binding"
|
|
||||||
"github.com/infraboard/mcube/v2/http/restful/response"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (h *UserRestfulApiHandler) QueryUser(r *restful.Request, w *restful.Response) {
|
|
||||||
// 补充下 Token校验
|
|
||||||
// 作为一个开发者, 业务接口开发代码里面,需要补充认证
|
|
||||||
// 通过中间件 来 剥离开 用户认证逻辑:
|
|
||||||
// 站在一个库作者的角度 来设计一个 认证的使用方式, 能不能 通过开关来控制一个接口需不需要被保护(on/off)
|
|
||||||
// 这个开关应该加那里? 接口描述(接口的装饰信息)
|
|
||||||
|
|
||||||
// 获取用户通过API传入的参数
|
|
||||||
req := user.NewQueryUserRequest()
|
|
||||||
|
|
||||||
// r.QueryParameter("page_size")
|
|
||||||
// r.QueryParameter("page_number")
|
|
||||||
// url bind, url parameter <---> obj form:"page_size" form:"page_number"
|
|
||||||
// url?
|
|
||||||
// gin bind 的具体实现:非简单结构: json user_ids = [] user_id=1&user_id=2
|
|
||||||
if err := binding.Query.Bind(r.Request, req); err != nil {
|
|
||||||
response.Failed(w, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
set, err := h.svc.QueryUser(r.Request.Context(), req)
|
|
||||||
if err != nil {
|
|
||||||
response.Failed(w, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 专门做脱敏处理
|
|
||||||
// for user.password = "" json: omitempty
|
|
||||||
// 每个接口 都需要定制化的写这些逻辑
|
|
||||||
// 为对象实现一个脱名方法: Densence, 断言这个对象实现了这个方法
|
|
||||||
// 定义一个接口,断言这个对象 满足这个接口, 这个能解决80%的问题
|
|
||||||
// 对象嵌套, 你需要自己 去调用嵌套对象的 Densence
|
|
||||||
|
|
||||||
// 能不能直接通过JSON标签 这样方式来完成脱敏: must:"3,4" (181*****4777)
|
|
||||||
// 不能每次都调用吧,因此这个脱敏逻辑 放到 Rsep函数内进行处理
|
|
||||||
//
|
|
||||||
response.Success(w, set)
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
<mxfile host="65bd71144e">
|
|
||||||
<diagram id="DRQy-UvMks4-KcdwQQVb" name="第 1 页">
|
|
||||||
<mxGraphModel dx="892" dy="370" 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="" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="130" y="230" width="410" height="120" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="3" value="user" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="200" y="260" width="120" height="60" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="15" style="edgeStyle=none;html=1;exitX=0.25;exitY=1;exitDx=0;exitDy=0;entryX=0.25;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="4" target="2">
|
|
||||||
<mxGeometry relative="1" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="4" value="" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="130" y="110" width="410" height="70" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="6" value="API" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="30" y="130" width="60" height="30" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="7" value="接口" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="40" y="270" width="60" height="30" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="9" style="edgeStyle=none;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="8" target="3">
|
|
||||||
<mxGeometry relative="1" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="8" value="token" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="400" y="260" width="120" height="60" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="13" style="edgeStyle=none;html=1;exitX=0;exitY=0.3333333333333333;exitDx=0;exitDy=0;exitPerimeter=0;" edge="1" parent="1" source="10" target="11">
|
|
||||||
<mxGeometry relative="1" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="10" value="Actor" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="630" y="10" width="30" height="60" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="14" style="edgeStyle=none;html=1;exitX=0.25;exitY=1;exitDx=0;exitDy=0;entryX=0.25;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="11" target="4">
|
|
||||||
<mxGeometry relative="1" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="11" value="" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="130" y="10" width="410" height="70" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="12" value="UI" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="40" y="25" width="60" height="30" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="16" value="<h1 style="margin-top: 0px;">脱敏</h1><p>数据出去时</p>" style="text;html=1;whiteSpace=wrap;overflow=hidden;rounded=0;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="580" y="100" width="180" height="120" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
</root>
|
|
||||||
</mxGraphModel>
|
|
||||||
</diagram>
|
|
||||||
</mxfile>
|
|
@ -13,6 +13,6 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
test.DevelopmentSetUp()
|
test.DevelopmentSet()
|
||||||
impl = user.GetService()
|
impl = user.GetService()
|
||||||
}
|
}
|
||||||
|
@ -33,13 +33,13 @@ type Service interface {
|
|||||||
|
|
||||||
func NewQueryUserRequest() *QueryUserRequest {
|
func NewQueryUserRequest() *QueryUserRequest {
|
||||||
return &QueryUserRequest{
|
return &QueryUserRequest{
|
||||||
PageRequest: *request.NewDefaultPageRequest(),
|
PageRequest: request.NewDefaultPageRequest(),
|
||||||
UserIds: []uint64{},
|
UserIds: []uint64{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type QueryUserRequest struct {
|
type QueryUserRequest struct {
|
||||||
request.PageRequest
|
*request.PageRequest
|
||||||
UserIds []uint64 `form:"user" json:"user"`
|
UserIds []uint64 `form:"user" json:"user"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ type CreateUserRequest struct {
|
|||||||
// 用户名
|
// 用户名
|
||||||
UserName string `json:"user_name" gorm:"column:user_name;type:varchar(100);not null;uniqueIndex" description:"用户名"`
|
UserName string `json:"user_name" gorm:"column:user_name;type:varchar(100);not null;uniqueIndex" description:"用户名"`
|
||||||
// 密码(Hash过后的)
|
// 密码(Hash过后的)
|
||||||
Password string `json:"password,omitempty" gorm:"column:password;type:varchar(200);not null" description:"用户密码" mask:",3,4"`
|
Password string `json:"password" gorm:"column:password;type:varchar(200);not null" description:"用户密码"`
|
||||||
// 用户描述
|
// 用户描述
|
||||||
Description string `json:"description" gorm:"column:description;type:varchar(200);not null" description:"用户描述"`
|
Description string `json:"description" gorm:"column:description;type:varchar(200);not null" description:"用户描述"`
|
||||||
// 用户类型
|
// 用户类型
|
||||||
|
@ -1,60 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
@ -1,64 +0,0 @@
|
|||||||
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"`
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
<mxfile host="65bd71144e">
|
<mxfile host="65bd71144e">
|
||||||
<diagram id="kBUSl4Twz2xUubsumQ87" name="第 1 页">
|
<diagram id="kBUSl4Twz2xUubsumQ87" name="第 1 页">
|
||||||
<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">
|
<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">
|
||||||
<root>
|
<root>
|
||||||
<mxCell id="0"/>
|
<mxCell id="0"/>
|
||||||
<mxCell id="1" parent="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">
|
<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"/>
|
<mxGeometry x="120" y="830" width="120" height="45" as="geometry"/>
|
||||||
</mxCell>
|
</mxCell>
|
||||||
<mxCell id="40" value="授权策略<div>用户的授权策略</div><div>User, 空间, Role</div>" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
<mxCell id="40" value="授权策略<div>用户的授权策略</div><div>User, Name, 接口</div>" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
||||||
<mxGeometry x="294" y="830" width="120" height="50" as="geometry"/>
|
<mxGeometry x="294" y="830" width="120" height="50" as="geometry"/>
|
||||||
</mxCell>
|
</mxCell>
|
||||||
<mxCell id="42" value="空间<div>Namepsace</div>" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
<mxCell id="42" value="空间<div>Namepsace</div>" 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">
|
<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"/>
|
<mxGeometry x="760" y="825" width="120" height="55" as="geometry"/>
|
||||||
</mxCell>
|
</mxCell>
|
||||||
<mxCell id="45" value="" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
<mxCell id="45" value="" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
<mxGeometry x="100" y="1090" width="330" height="180" as="geometry"/>
|
<mxGeometry x="100" y="1090" width="330" height="180" as="geometry"/>
|
||||||
</mxCell>
|
</mxCell>
|
||||||
<mxCell id="46" value="用户认证" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
|
<mxCell id="46" value="用户认证" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||||
<mxGeometry x="100" y="1060" width="60" height="30" as="geometry"/>
|
<mxGeometry x="100" y="1060" width="60" height="30" as="geometry"/>
|
||||||
</mxCell>
|
</mxCell>
|
||||||
<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">
|
<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">
|
||||||
<mxGeometry relative="1" as="geometry"/>
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
</mxCell>
|
</mxCell>
|
||||||
<mxCell id="47" value="token" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
<mxCell id="47" value="token" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
<mxGeometry x="280" y="1119" width="120" height="50" as="geometry"/>
|
<mxGeometry x="280" y="1119" width="120" height="50" as="geometry"/>
|
||||||
</mxCell>
|
</mxCell>
|
||||||
<mxCell id="48" value="user" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
|
<mxCell id="48" value="user" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
<mxGeometry x="120" y="1119" width="120" height="50" as="geometry"/>
|
<mxGeometry x="120" y="1119" width="120" height="50" as="geometry"/>
|
||||||
</mxCell>
|
</mxCell>
|
||||||
</root>
|
</root>
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/infraboard/mcube/v2/ioc/server/cmd"
|
|
||||||
|
|
||||||
// 加载的业务对象
|
|
||||||
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps"
|
|
||||||
|
|
||||||
// 非功能性模块
|
|
||||||
_ "github.com/infraboard/mcube/v2/ioc/apps/apidoc/restful"
|
|
||||||
_ "github.com/infraboard/mcube/v2/ioc/apps/health/restful"
|
|
||||||
_ "github.com/infraboard/mcube/v2/ioc/apps/metric/restful"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// 启动
|
|
||||||
cmd.Start()
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
# 接口鉴权工具(中间件)
|
|
||||||
|
|
||||||
|
|
||||||
1. 路有装饰, 路有配置
|
|
||||||
```go
|
|
||||||
// required_auth=true/false
|
|
||||||
ws.Route(ws.GET("").To(h.QueryUser).
|
|
||||||
Doc("用户列表查询").
|
|
||||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
|
||||||
// 这个开关怎么生效
|
|
||||||
// 中间件需求读取接口的描述信息,来决定是否需要认证
|
|
||||||
Metadata(permission.Auth(true)).
|
|
||||||
Param(restful.QueryParameter("page_size", "分页大小").DataType("integer")).
|
|
||||||
Param(restful.QueryParameter("page_number", "页码").DataType("integer")).
|
|
||||||
Writes(Set{}).
|
|
||||||
Returns(200, "OK", Set{}))
|
|
||||||
```
|
|
||||||
|
|
||||||
2. 加载鉴权处理逻辑(中间件)
|
|
@ -1,120 +0,0 @@
|
|||||||
package permission
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/endpoint"
|
|
||||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/token"
|
|
||||||
"github.com/emicklei/go-restful/v3"
|
|
||||||
"github.com/infraboard/mcube/v2/exception"
|
|
||||||
"github.com/infraboard/mcube/v2/http/restful/response"
|
|
||||||
"github.com/infraboard/mcube/v2/ioc"
|
|
||||||
"github.com/infraboard/mcube/v2/ioc/config/gorestful"
|
|
||||||
"github.com/infraboard/mcube/v2/ioc/config/log"
|
|
||||||
"github.com/rs/zerolog"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
ioc.Config().Registry(&Checker{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func Auth(v bool) (string, bool) {
|
|
||||||
return endpoint.META_REQUIRED_AUTH_KEY, v
|
|
||||||
}
|
|
||||||
|
|
||||||
func Permission(v bool) (string, bool) {
|
|
||||||
return endpoint.META_REQUIRED_PERM_KEY, v
|
|
||||||
}
|
|
||||||
|
|
||||||
func Resource(v string) (string, string) {
|
|
||||||
return endpoint.META_RESOURCE_KEY, v
|
|
||||||
}
|
|
||||||
|
|
||||||
func Action(v string) (string, string) {
|
|
||||||
return endpoint.META_ACTION_KEY, v
|
|
||||||
}
|
|
||||||
|
|
||||||
// 这个中间件也是对象, 认证与鉴权
|
|
||||||
// 通过路由装饰 来当中开关,控制怎么认证,是否开启认证,是否开启坚强,角色标记
|
|
||||||
type Checker struct {
|
|
||||||
ioc.ObjectImpl
|
|
||||||
log *zerolog.Logger
|
|
||||||
|
|
||||||
token token.Service
|
|
||||||
// policy policy.Service
|
|
||||||
}
|
|
||||||
|
|
||||||
// 中间件对象名称
|
|
||||||
func (c *Checker) Name() string {
|
|
||||||
return "permission_checker"
|
|
||||||
}
|
|
||||||
|
|
||||||
// 对象初始化的优先级, 由于业务接口在Init函数里面 使用默认优先级0, 由大到小
|
|
||||||
// 框架是 899 898
|
|
||||||
// 框架的init函数调用完成,里面调用 这个对象的init函数, 实现了全局中间件
|
|
||||||
func (c *Checker) Priority() int {
|
|
||||||
return gorestful.Priority() - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Checker) Init() error {
|
|
||||||
c.log = log.Sub(c.Name())
|
|
||||||
c.token = token.GetService()
|
|
||||||
// c.policy = policy.GetService()
|
|
||||||
|
|
||||||
// 注册认证中间件
|
|
||||||
gorestful.RootRouter().Filter(c.Check)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 中间件的函数里面
|
|
||||||
func (c *Checker) Check(r *restful.Request, w *restful.Response, next *restful.FilterChain) {
|
|
||||||
// 请求处理前, 对接口进行保护
|
|
||||||
// 1. 知道用户当前访问的是哪个接口, 当前url 匹配到的路由是哪个
|
|
||||||
// SelectedRoute, 它可以返回当前URL适配哪个路有, RouteReader
|
|
||||||
// 封装了一个函数 来获取Meta信息 NewEntryFromRestRouteReader
|
|
||||||
route := endpoint.NewEntryFromRestRouteReader(r.SelectedRoute())
|
|
||||||
if route.RequiredAuth {
|
|
||||||
// 校验身份
|
|
||||||
tk, err := c.CheckToken(r)
|
|
||||||
if err != nil {
|
|
||||||
response.Failed(w, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 校验权限
|
|
||||||
if err := c.CheckPolicy(r, tk, route); err != nil {
|
|
||||||
response.Failed(w, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 请求处理
|
|
||||||
next.ProcessFilter(r, w)
|
|
||||||
|
|
||||||
// 请求处理后
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Checker) CheckToken(r *restful.Request) (*token.Token, error) {
|
|
||||||
v := token.GetAccessTokenFromHTTP(r.Request)
|
|
||||||
if v == "" {
|
|
||||||
return nil, exception.NewUnauthorized("请先登录")
|
|
||||||
}
|
|
||||||
|
|
||||||
tk, err := c.token.ValiateToken(r.Request.Context(), token.NewValiateTokenRequest(v))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果校验成功,需要把 用户的身份信息,放到请求的上下文中,方便后面的逻辑获取
|
|
||||||
// context.WithValue 来往ctx 添加 value
|
|
||||||
// key: value, value token对象
|
|
||||||
ctx := context.WithValue(r.Request.Context(), token.CTX_TOKEN_KEY, tk)
|
|
||||||
|
|
||||||
// ctx 生成一个新的,继续往下传递
|
|
||||||
r.Request = r.Request.WithContext(ctx)
|
|
||||||
return tk, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Checker) CheckPolicy(r *restful.Request, tk *token.Token, route *endpoint.RouteEntry) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,76 +0,0 @@
|
|||||||
<mxfile host="65bd71144e">
|
|
||||||
<diagram id="h2EQwLDlSJcMUsYSktMs" name="第 1 页">
|
|
||||||
<mxGraphModel dx="892" dy="370" 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="QuerUser" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="650" y="190" width="120" height="60" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="3" value="" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="490" y="70" width="70" height="310" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="5" value="Actor" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="710" y="70" width="30" height="60" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="6" value="A" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="740" y="85" width="60" height="30" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="7" value="Actor" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="330" y="10" width="30" height="60" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="8" value="B" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="370" y="30" width="60" height="30" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="9" value="Actor" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="80" y="115" width="30" height="60" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="10" value="" style="ellipse;shape=cloud;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="120" y="175" width="120" height="80" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="11" value="User" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="65" y="70" width="60" height="30" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="12" style="edgeStyle=none;html=1;exitX=1;exitY=0.3333333333333333;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.4;entryY=0.1;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="9" target="10">
|
|
||||||
<mxGeometry relative="1" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="21" style="edgeStyle=none;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="13" target="3">
|
|
||||||
<mxGeometry relative="1" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="13" value="API&nbsp;<div>Server</div>" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="400" y="70" width="70" height="310" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="14" style="edgeStyle=none;html=1;exitX=0.96;exitY=0.7;exitDx=0;exitDy=0;exitPerimeter=0;entryX=-0.002;entryY=0.525;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="10" target="13">
|
|
||||||
<mxGeometry relative="1" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="15" value="Request" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="150" y="100" width="60" height="30" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="16" style="edgeStyle=none;html=1;exitX=0;exitY=0.75;exitDx=0;exitDy=0;entryX=0.55;entryY=0.95;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="13" target="10">
|
|
||||||
<mxGeometry relative="1" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="17" value="Response" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="250" y="290" width="60" height="30" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="18" value="业务逻辑处理" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="700" y="300" width="60" height="30" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="19" value="" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="500" y="80" width="70" height="310" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="20" value="中间件<div>...</div>" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="510" y="90" width="70" height="310" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="22" style="edgeStyle=none;html=1;exitX=1;exitY=0.25;exitDx=0;exitDy=0;entryX=0.017;entryY=0.371;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="20" target="2">
|
|
||||||
<mxGeometry relative="1" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="23" style="edgeStyle=none;html=1;exitX=0;exitY=0.75;exitDx=0;exitDy=0;entryX=0.971;entryY=0.727;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="2" target="20">
|
|
||||||
<mxGeometry relative="1" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="24" value="Request, Response,<div>Next</div>" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="510" y="430" width="60" height="30" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
</root>
|
|
||||||
</mxGraphModel>
|
|
||||||
</diagram>
|
|
||||||
</mxfile>
|
|
@ -1,49 +0,0 @@
|
|||||||
package permission
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/endpoint"
|
|
||||||
"github.com/infraboard/mcube/v2/ioc"
|
|
||||||
"github.com/infraboard/mcube/v2/ioc/config/gorestful"
|
|
||||||
"github.com/infraboard/mcube/v2/ioc/config/log"
|
|
||||||
"github.com/rs/zerolog"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
ioc.Api().Registry(&ApiRegister{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetApiRegister() *ApiRegister {
|
|
||||||
return ioc.Api().Get("api_register").(*ApiRegister)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 接口注册模块: 扫描当前GoResuful Container下所有路径,并完成注册
|
|
||||||
type ApiRegister struct {
|
|
||||||
ioc.ObjectImpl
|
|
||||||
|
|
||||||
log *zerolog.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ApiRegister) Name() string {
|
|
||||||
return "api_register"
|
|
||||||
}
|
|
||||||
|
|
||||||
// 这个Init一定要放到所有的路由都添加完成后进行
|
|
||||||
func (i *ApiRegister) Priority() int {
|
|
||||||
return -100
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *ApiRegister) Init() error {
|
|
||||||
a.log = log.Sub(a.Name())
|
|
||||||
// 注册认证中间件
|
|
||||||
entries := endpoint.NewEntryFromRestfulContainer(gorestful.RootRouter())
|
|
||||||
req := endpoint.NewRegistryEndpointRequest()
|
|
||||||
req.AddItem(entries...)
|
|
||||||
set, err := endpoint.GetService().RegistryEndpoint(context.Background(), req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.log.Info().Msgf("registry endpoinst: %s", set.Items)
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -10,7 +10,7 @@ import (
|
|||||||
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps"
|
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps"
|
||||||
)
|
)
|
||||||
|
|
||||||
func DevelopmentSetUp() {
|
func DevelopmentSet() {
|
||||||
// import 后自动执行的逻辑
|
// import 后自动执行的逻辑
|
||||||
// 工具对象的初始化, 需要的是绝对路径
|
// 工具对象的初始化, 需要的是绝对路径
|
||||||
ioc.DevelopmentSetupWithPath(os.Getenv("CONFIG_PATH"))
|
ioc.DevelopmentSetupWithPath(os.Getenv("CONFIG_PATH"))
|
||||||
|
6
go.mod
6
go.mod
@ -4,15 +4,13 @@ go 1.24.1
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/caarlos0/env/v6 v6.10.1
|
github.com/caarlos0/env/v6 v6.10.1
|
||||||
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/gin-gonic/gin v1.10.0
|
||||||
github.com/google/uuid v1.6.0
|
|
||||||
github.com/infraboard/mcube/v2 v2.0.59
|
github.com/infraboard/mcube/v2 v2.0.59
|
||||||
github.com/infraboard/modules v0.0.12
|
github.com/infraboard/modules v0.0.12
|
||||||
github.com/rs/zerolog v1.34.0
|
github.com/rs/zerolog v1.34.0
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
golang.org/x/crypto v0.38.0
|
golang.org/x/crypto v0.38.0
|
||||||
|
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
gorm.io/driver/mysql v1.5.7
|
gorm.io/driver/mysql v1.5.7
|
||||||
gorm.io/gorm v1.26.0
|
gorm.io/gorm v1.26.0
|
||||||
@ -43,6 +41,7 @@ require (
|
|||||||
github.com/go-playground/validator/v10 v10.26.0 // indirect
|
github.com/go-playground/validator/v10 v10.26.0 // indirect
|
||||||
github.com/go-sql-driver/mysql v1.7.0 // indirect
|
github.com/go-sql-driver/mysql v1.7.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.5 // 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/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
@ -77,7 +76,6 @@ require (
|
|||||||
github.com/uptrace/opentelemetry-go-extra/otelgorm v0.3.2 // indirect
|
github.com/uptrace/opentelemetry-go-extra/otelgorm v0.3.2 // indirect
|
||||||
github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2 // indirect
|
github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2 // indirect
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful v0.60.0 // indirect
|
|
||||||
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.60.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.60.0 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
|
||||||
go.opentelemetry.io/otel v1.35.0 // indirect
|
go.opentelemetry.io/otel v1.35.0 // indirect
|
||||||
|
31
go.sum
31
go.sum
@ -18,17 +18,11 @@ github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJ
|
|||||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/emicklei/go-restful-openapi/v2 v2.11.0 h1:Ur+yGxoOH/7KRmcj/UoMFqC3VeNc9VOe+/XidumxTvk=
|
|
||||||
github.com/emicklei/go-restful-openapi/v2 v2.11.0/go.mod h1:4CTuOXHFg3jkvCpnXN+Wkw5prVUnP8hIACssJTYorWo=
|
|
||||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
|
||||||
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
|
|
||||||
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||||
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
|
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
|
||||||
@ -44,18 +38,12 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
|||||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
|
||||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
|
||||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||||
github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
|
|
||||||
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
||||||
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
||||||
github.com/go-openapi/spec v0.20.9/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
|
|
||||||
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
|
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
|
||||||
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
|
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
|
||||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
|
||||||
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
|
||||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
@ -111,20 +99,14 @@ github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02
|
|||||||
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
|
||||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
|
||||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
|
||||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
@ -142,7 +124,6 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
|
|||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
@ -175,7 +156,6 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
|
|||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
@ -193,14 +173,10 @@ github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2 h1:ZjUj9BLYf9PEqBn8W/Oa
|
|||||||
github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2/go.mod h1:O8bHQfyinKwTXKkiKNGmLQS7vRsqRxIQTFZpYpHK3IQ=
|
github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2/go.mod h1:O8bHQfyinKwTXKkiKNGmLQS7vRsqRxIQTFZpYpHK3IQ=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||||
go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful v0.60.0 h1:jvVaMcOzU4abk6o8VCJJN5wRoFQxe3Afnc8fPcYbO0I=
|
|
||||||
go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful v0.60.0/go.mod h1:NMt0e4UA3rY+CV+xyjE4wLhz/6BiydSOZhMeevBDZB0=
|
|
||||||
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.60.0 h1:jj/B7eX95/mOxim9g9laNZkOHKz/XCHG0G410SntRy4=
|
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.60.0 h1:jj/B7eX95/mOxim9g9laNZkOHKz/XCHG0G410SntRy4=
|
||||||
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.60.0/go.mod h1:ZvRTVaYYGypytG0zRp2A60lpj//cMq3ZnxYdZaljVBM=
|
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.60.0/go.mod h1:ZvRTVaYYGypytG0zRp2A60lpj//cMq3ZnxYdZaljVBM=
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
|
||||||
go.opentelemetry.io/contrib/propagators/b3 v1.35.0 h1:DpwKW04LkdFRFCIgM3sqwTJA/QREHMeMHYPWP1WeaPQ=
|
|
||||||
go.opentelemetry.io/contrib/propagators/b3 v1.35.0/go.mod h1:9+SNxwqvCWo1qQwUpACBY5YKNVxFJn5mlbXg/4+uKBg=
|
|
||||||
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
|
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
|
||||||
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
|
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
|
||||||
@ -223,6 +199,8 @@ golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw=
|
|||||||
golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
|
golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
|
||||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||||
|
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI=
|
||||||
|
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ=
|
||||||
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
||||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||||
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||||
@ -243,16 +221,11 @@ google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3i
|
|||||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
|
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
|
||||||
|
@ -15,9 +15,7 @@
|
|||||||
项目名称: devcloud
|
项目名称: devcloud
|
||||||
|
|
||||||
+ [GO18015-鑫](https://github.com/sword-demon/go18)
|
+ [GO18015-鑫](https://github.com/sword-demon/go18)
|
||||||
|
+ [GO18012-Leowy](https://gitee.com/leo_wy/GoProjects.git)
|
||||||
+ [GO18005-康](https://github.com/YouthInThinking/GoProject.git)
|
+ [GO18005-康](https://github.com/YouthInThinking/GoProject.git)
|
||||||
+ [GO18007-磊](https://gitee.com/wangleisir/mage_go_project.git)
|
+ [GO18007-磊](https://gitee.com/wangleisir/mage_go_project.git)
|
||||||
|
|
||||||
+ [GO18012-Leowy](https://gitee.com/leo_wy/GoProjects.git)
|
|
||||||
+ [硕](https://gitee.com/liushuowalk/kubernetersgo)
|
+ [硕](https://gitee.com/liushuowalk/kubernetersgo)
|
||||||
|
|
||||||
|
@ -1,181 +0,0 @@
|
|||||||
# gorestful Web框架
|
|
||||||
|
|
||||||
[go-restful](https://github.com/emicklei/go-restful)
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
restfulspec "github.com/emicklei/go-restful-openapi/v2"
|
|
||||||
restful "github.com/emicklei/go-restful/v3"
|
|
||||||
"github.com/go-openapi/spec"
|
|
||||||
)
|
|
||||||
|
|
||||||
// UserResource is the REST layer to the User domain
|
|
||||||
type UserResource struct {
|
|
||||||
// normally one would use DAO (data access object)
|
|
||||||
users map[string]User
|
|
||||||
}
|
|
||||||
|
|
||||||
// WebService creates a new service that can handle REST requests for User resources.
|
|
||||||
func (u UserResource) WebService() *restful.WebService {
|
|
||||||
ws := new(restful.WebService)
|
|
||||||
ws.
|
|
||||||
Path("/users").
|
|
||||||
Consumes(restful.MIME_XML, restful.MIME_JSON).
|
|
||||||
Produces(restful.MIME_JSON, restful.MIME_XML) // you can specify this per route as well
|
|
||||||
|
|
||||||
tags := []string{"users"}
|
|
||||||
|
|
||||||
ws.Route(ws.GET("/").To(u.findAllUsers).
|
|
||||||
// docs
|
|
||||||
Doc("get all users").
|
|
||||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
|
||||||
Writes([]User{}).
|
|
||||||
Returns(200, "OK", []User{}))
|
|
||||||
|
|
||||||
ws.Route(ws.GET("/{user-id}").To(u.findUser).
|
|
||||||
// docs
|
|
||||||
Doc("get a user").
|
|
||||||
Param(ws.PathParameter("user-id", "identifier of the user").DataType("integer").DefaultValue("1")).
|
|
||||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
|
||||||
Writes(User{}). // on the response
|
|
||||||
Returns(200, "OK", User{}).
|
|
||||||
Returns(404, "Not Found", nil))
|
|
||||||
|
|
||||||
ws.Route(ws.PUT("/{user-id}").To(u.upsertUser).
|
|
||||||
// docs
|
|
||||||
Doc("update a user").
|
|
||||||
Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")).
|
|
||||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
|
||||||
Reads(User{})) // from the request
|
|
||||||
|
|
||||||
ws.Route(ws.POST("").To(u.createUser).
|
|
||||||
// docs
|
|
||||||
Doc("create a user").
|
|
||||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
|
||||||
Reads(User{})) // from the request
|
|
||||||
|
|
||||||
ws.Route(ws.DELETE("/{user-id}").To(u.removeUser).
|
|
||||||
// docs
|
|
||||||
Doc("delete a user").
|
|
||||||
Metadata(restfulspec.KeyOpenAPITags, tags).
|
|
||||||
Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")))
|
|
||||||
|
|
||||||
return ws
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET http://localhost:8080/users
|
|
||||||
func (u UserResource) findAllUsers(request *restful.Request, response *restful.Response) {
|
|
||||||
log.Println("findAllUsers")
|
|
||||||
list := []User{}
|
|
||||||
for _, each := range u.users {
|
|
||||||
list = append(list, each)
|
|
||||||
}
|
|
||||||
response.WriteEntity(list)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET http://localhost:8080/users/1
|
|
||||||
func (u UserResource) findUser(request *restful.Request, response *restful.Response) {
|
|
||||||
log.Println("findUser")
|
|
||||||
id := request.PathParameter("user-id")
|
|
||||||
usr := u.users[id]
|
|
||||||
if len(usr.ID) == 0 {
|
|
||||||
response.WriteErrorString(http.StatusNotFound, "User could not be found.")
|
|
||||||
} else {
|
|
||||||
response.WriteEntity(usr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PUT http://localhost:8080/users/1
|
|
||||||
// <User><Id>1</Id><Name>Melissa Raspberry</Name></User>
|
|
||||||
func (u *UserResource) upsertUser(request *restful.Request, response *restful.Response) {
|
|
||||||
log.Println("upsertUser")
|
|
||||||
usr := User{ID: request.PathParameter("user-id")}
|
|
||||||
err := request.ReadEntity(&usr)
|
|
||||||
if err == nil {
|
|
||||||
u.users[usr.ID] = usr
|
|
||||||
response.WriteEntity(usr)
|
|
||||||
} else {
|
|
||||||
response.WriteError(http.StatusInternalServerError, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// POST http://localhost:8080/users
|
|
||||||
// <User><Id>1</Id><Name>Melissa</Name></User>
|
|
||||||
func (u *UserResource) createUser(request *restful.Request, response *restful.Response) {
|
|
||||||
log.Println("createUser")
|
|
||||||
usr := User{ID: fmt.Sprintf("%d", time.Now().Unix())}
|
|
||||||
err := request.ReadEntity(&usr)
|
|
||||||
if err == nil {
|
|
||||||
u.users[usr.ID] = usr
|
|
||||||
response.WriteHeaderAndEntity(http.StatusCreated, usr)
|
|
||||||
} else {
|
|
||||||
response.WriteError(http.StatusInternalServerError, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DELETE http://localhost:8080/users/1
|
|
||||||
func (u *UserResource) removeUser(request *restful.Request, response *restful.Response) {
|
|
||||||
log.Println("removeUser")
|
|
||||||
id := request.PathParameter("user-id")
|
|
||||||
delete(u.users, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
u := UserResource{map[string]User{}}
|
|
||||||
restful.DefaultContainer.Add(u.WebService())
|
|
||||||
|
|
||||||
config := restfulspec.Config{
|
|
||||||
WebServices: restful.RegisteredWebServices(), // you control what services are visible
|
|
||||||
APIPath: "/apidocs.json",
|
|
||||||
PostBuildSwaggerObjectHandler: enrichSwaggerObject}
|
|
||||||
restful.DefaultContainer.Add(restfulspec.NewOpenAPIService(config))
|
|
||||||
|
|
||||||
// Optionally, you can install the Swagger Service which provides a nice Web UI on your REST API
|
|
||||||
// You need to download the Swagger HTML5 assets and change the FilePath location in the config below.
|
|
||||||
// Open http://localhost:8080/apidocs/?url=http://localhost:8080/apidocs.json
|
|
||||||
http.Handle("/apidocs/", http.StripPrefix("/apidocs/", http.FileServer(http.Dir("/Users/emicklei/Projects/swagger-ui/dist"))))
|
|
||||||
|
|
||||||
log.Printf("start listening on localhost:8080")
|
|
||||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
func enrichSwaggerObject(swo *spec.Swagger) {
|
|
||||||
swo.Info = &spec.Info{
|
|
||||||
InfoProps: spec.InfoProps{
|
|
||||||
Title: "UserService",
|
|
||||||
Description: "Resource for managing Users",
|
|
||||||
Contact: &spec.ContactInfo{
|
|
||||||
ContactInfoProps: spec.ContactInfoProps{
|
|
||||||
Name: "john",
|
|
||||||
Email: "john@doe.rp",
|
|
||||||
URL: "http://johndoe.org",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
License: &spec.License{
|
|
||||||
LicenseProps: spec.LicenseProps{
|
|
||||||
Name: "MIT",
|
|
||||||
URL: "http://mit.org",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Version: "1.0.0",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
swo.Tags = []spec.Tag{spec.Tag{TagProps: spec.TagProps{
|
|
||||||
Name: "users",
|
|
||||||
Description: "Managing users"}}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// User is just a sample type
|
|
||||||
type User struct {
|
|
||||||
ID string `xml:"id" json:"id" description:"identifier of the user"`
|
|
||||||
Name string `xml:"name" json:"name" description:"name of the user" default:"john"`
|
|
||||||
Age int `xml:"age" json:"age" description:"age of the user" default:"21"`
|
|
||||||
}
|
|
||||||
```
|
|
@ -1,85 +0,0 @@
|
|||||||
<mxfile host="65bd71144e">
|
|
||||||
<diagram id="EsWSgBRg6HmPYJxvZoKw" name="第 1 页">
|
|
||||||
<mxGraphModel dx="812" dy="419" 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="*gin.Engine<div>Server</div>" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="120" y="140" width="210" height="60" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="3" value="Gin" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="40" y="90" width="60" height="30" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="4" value="GoRestful" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="650" y="100" width="60" height="30" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="5" value="<div><font color="#000000">restful.DefaultContainer</font></div><div>Server</div>" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="414" y="140" width="210" height="60" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="6" value="" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="120" y="230" width="210" height="100" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="7" value="Group" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="50" y="230" width="60" height="30" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="8" value="group<div>/books</div>" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="140" y="250" width="80" height="60" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="9" value="group<div>/comments</div>" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="240" y="250" width="80" height="60" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="10" value="" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="414" y="230" width="210" height="100" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="11" value="WebService" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="650" y="230" width="60" height="30" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="12" value="ws<div>/books</div>" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="440" y="250" width="80" height="60" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="13" value="ws<div>/comments</div>" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="530" y="250" width="80" height="60" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="14" value="restful.DefaultContainer.Add(u.WebService())" style="text;whiteSpace=wrap;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="557" y="50" width="270" height="40" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="15" value="root.Group("")" style="text;whiteSpace=wrap;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="100" y="50" width="270" height="40" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="16" value="" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="120" y="360" width="210" height="100" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="17" value="Route" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="50" y="360" width="60" height="30" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="18" value="route<div>get('/')</div>" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="150" y="380" width="70" height="40" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="19" value="route<div>get('/')</div>" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="245" y="380" width="70" height="40" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="20" value="" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="414" y="360" width="210" height="100" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="21" value="Route" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="660" y="360" width="60" height="30" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="22" value="ws.GET("/").To(u.findAllUsers)" style="text;whiteSpace=wrap;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="627" y="330" width="200" height="40" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="23" value="route<div>ws.GET('/')</div>" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="430" y="380" width="70" height="40" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="24" value="route<div>ws.POST('/')</div>" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="540" y="380" width="70" height="40" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="25" value="findAllUsers(request *restful.Request, response *restful.Response)" style="text;whiteSpace=wrap;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="400" y="20" width="390" height="40" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="26" value="<div style="color: #f8f8f2;background-color: #272822;font-family: 'Cascadia Code NF', Menlo, Monaco, 'Courier New', monospace, Menlo, Monaco, 'Courier New', monospace;font-weight: normal;font-size: 12px;line-height: 18px;white-space: pre;"><div><span style="color: #a6e22e;">queryBook</span><span style="color: #f8f8f2;">(</span><span style="color: #fd971f;font-style: italic;">ctx</span><span style="color: #f8f8f2;"> </span><span style="color: #f92672;">*</span><span style="color: #a6e22e;text-decoration: underline;">gin</span><span style="color: #f8f8f2;">.</span><span style="color: #a6e22e;text-decoration: underline;">Context</span><span style="color: #f8f8f2;">)</span></div></div>" style="text;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="100" y="20" width="220" height="40" as="geometry"/>
|
|
||||||
</mxCell>
|
|
||||||
</root>
|
|
||||||
</mxGraphModel>
|
|
||||||
</diagram>
|
|
||||||
</mxfile>
|
|
Loading…
x
Reference in New Issue
Block a user