Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
d10c26eb3b | |||
f1d8684ab7 | |||
9b8b96856c | |||
c315c4747c |
@ -1,7 +1,7 @@
|
||||
[app]
|
||||
name = "devcloud"
|
||||
description = "app desc"
|
||||
address = "localhost"
|
||||
address = "http://127.0.0.1:8080"
|
||||
encrypt_key = "defualt app encrypt key"
|
||||
|
||||
[datasource]
|
||||
|
@ -1,2 +1,17 @@
|
||||
# 接口管理
|
||||
|
||||
如何提取 当前这个服务的路由条目, 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
|
||||
}
|
||||
```
|
||||
|
||||
|
19
devcloud/mcenter/apps/endpoint/enum.go
Normal file
19
devcloud/mcenter/apps/endpoint/enum.go
Normal file
@ -0,0 +1,19 @@
|
||||
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"
|
||||
)
|
92
devcloud/mcenter/apps/endpoint/impl/endpoint.go
Normal file
92
devcloud/mcenter/apps/endpoint/impl/endpoint.go
Normal file
@ -0,0 +1,92 @@
|
||||
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
|
||||
}
|
34
devcloud/mcenter/apps/endpoint/impl/impl.go
Normal file
34
devcloud/mcenter/apps/endpoint/impl/impl.go
Normal file
@ -0,0 +1,34 @@
|
||||
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
|
||||
}
|
77
devcloud/mcenter/apps/endpoint/interface.go
Normal file
77
devcloud/mcenter/apps/endpoint/interface.go
Normal file
@ -0,0 +1,77 @@
|
||||
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)
|
||||
}
|
251
devcloud/mcenter/apps/endpoint/model.go
Normal file
251
devcloud/mcenter/apps/endpoint/model.go
Normal file
@ -0,0 +1,251 @@
|
||||
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,6 +7,10 @@ import (
|
||||
_ "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/endpoint/impl"
|
||||
|
||||
// 颁发器
|
||||
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps/token/issuers"
|
||||
// 鉴权中间件
|
||||
_ "122.51.31.227/go-course/go18/devcloud/mcenter/permission"
|
||||
)
|
||||
|
@ -33,3 +33,28 @@ 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,6 +1,8 @@
|
||||
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"
|
||||
@ -9,27 +11,31 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
ioc.Api().Registry(&TokenRestulApiHandler{})
|
||||
ioc.Api().Registry(&TokenRestfulApiHandler{})
|
||||
}
|
||||
|
||||
type TokenRestulApiHandler struct {
|
||||
type TokenRestfulApiHandler struct {
|
||||
ioc.ObjectImpl
|
||||
|
||||
// 依赖控制器
|
||||
svc token.Service
|
||||
}
|
||||
|
||||
func (h *TokenRestulApiHandler) Name() string {
|
||||
func (h *TokenRestfulApiHandler) Name() string {
|
||||
return token.APP_NAME
|
||||
}
|
||||
|
||||
func (h *TokenRestulApiHandler) Init() error {
|
||||
//go:embed docs/login.md
|
||||
var loginApiDocNotes string
|
||||
|
||||
func (h *TokenRestfulApiHandler) Init() error {
|
||||
h.svc = token.GetService()
|
||||
|
||||
tags := []string{"用户登录"}
|
||||
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{}).
|
||||
|
8
devcloud/mcenter/apps/token/api/docs/login.md
Normal file
8
devcloud/mcenter/apps/token/api/docs/login.md
Normal file
@ -0,0 +1,8 @@
|
||||
登录接口
|
||||
|
||||
```json
|
||||
{
|
||||
"username": "admin",
|
||||
"password": "123456"
|
||||
}
|
||||
```
|
@ -10,7 +10,7 @@ import (
|
||||
"github.com/infraboard/mcube/v2/ioc/config/application"
|
||||
)
|
||||
|
||||
func (h *TokenRestulApiHandler) Login(r *restful.Request, w *restful.Response) {
|
||||
func (h *TokenRestfulApiHandler) Login(r *restful.Request, w *restful.Response) {
|
||||
// 1. 获取用户的请求参数, 参数在Body里面
|
||||
req := token.NewIssueTokenRequest()
|
||||
|
||||
@ -78,7 +78,7 @@ func (h *TokenRestulApiHandler) Login(r *restful.Request, w *restful.Response) {
|
||||
// }
|
||||
|
||||
// Logout HandleFunc
|
||||
func (h *TokenRestulApiHandler) Logout(r *restful.Request, w *restful.Response) {
|
||||
func (h *TokenRestfulApiHandler) Logout(r *restful.Request, w *restful.Response) {
|
||||
req := token.NewRevolkTokenRequest(
|
||||
token.GetAccessTokenFromHTTP(r.Request),
|
||||
token.GetRefreshTokenFromHTTP(r.Request),
|
||||
@ -106,7 +106,7 @@ func (h *TokenRestulApiHandler) Logout(r *restful.Request, w *restful.Response)
|
||||
response.Success(w, tk)
|
||||
}
|
||||
|
||||
func (h *TokenRestulApiHandler) ValiateToken(r *restful.Request, w *restful.Response) {
|
||||
func (h *TokenRestfulApiHandler) ValiateToken(r *restful.Request, w *restful.Response) {
|
||||
// 1. 获取用户的请求参数, 参数在Body里面
|
||||
req := token.NewValiateTokenRequest("")
|
||||
err := r.ReadEntity(req)
|
||||
|
@ -22,6 +22,7 @@ func GetAccessTokenFromHTTP(r *http.Request) string {
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
// ?token=xxxx
|
||||
tk, _ = url.QueryUnescape(cookie.Value)
|
||||
} else {
|
||||
// 处理 带格式: Bearer <Your API key>
|
||||
@ -33,6 +34,7 @@ func GetAccessTokenFromHTTP(r *http.Request) string {
|
||||
return tk
|
||||
}
|
||||
|
||||
// 从上下文中 提取 用户身份信息
|
||||
func GetTokenFromCtx(ctx context.Context) *Token {
|
||||
if v := ctx.Value(CTX_TOKEN_KEY); v != nil {
|
||||
return v.(*Token)
|
||||
|
@ -65,3 +65,27 @@ func (u *User) CheckPassword(password string) error {
|
||||
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 +1,55 @@
|
||||
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"`
|
||||
}
|
||||
|
47
devcloud/mcenter/apps/user/api/user.go
Normal file
47
devcloud/mcenter/apps/user/api/user.go
Normal file
@ -0,0 +1,47 @@
|
||||
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)
|
||||
}
|
52
devcloud/mcenter/apps/user/design.drawio
Normal file
52
devcloud/mcenter/apps/user/design.drawio
Normal file
@ -0,0 +1,52 @@
|
||||
<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>
|
@ -33,13 +33,13 @@ type Service interface {
|
||||
|
||||
func NewQueryUserRequest() *QueryUserRequest {
|
||||
return &QueryUserRequest{
|
||||
PageRequest: request.NewDefaultPageRequest(),
|
||||
PageRequest: *request.NewDefaultPageRequest(),
|
||||
UserIds: []uint64{},
|
||||
}
|
||||
}
|
||||
|
||||
type QueryUserRequest struct {
|
||||
*request.PageRequest
|
||||
request.PageRequest
|
||||
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:"用户名"`
|
||||
// 密码(Hash过后的)
|
||||
Password string `json:"password" gorm:"column:password;type:varchar(200);not null" description:"用户密码"`
|
||||
Password string `json:"password,omitempty" gorm:"column:password;type:varchar(200);not null" description:"用户密码" mask:",3,4"`
|
||||
// 用户描述
|
||||
Description string `json:"description" gorm:"column:description;type:varchar(200);not null" description:"用户描述"`
|
||||
// 用户类型
|
||||
|
18
devcloud/mcenter/main.go
Normal file
18
devcloud/mcenter/main.go
Normal file
@ -0,0 +1,18 @@
|
||||
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()
|
||||
}
|
19
devcloud/mcenter/permission/README.md
Normal file
19
devcloud/mcenter/permission/README.md
Normal file
@ -0,0 +1,19 @@
|
||||
# 接口鉴权工具(中间件)
|
||||
|
||||
|
||||
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. 加载鉴权处理逻辑(中间件)
|
120
devcloud/mcenter/permission/checker.go
Normal file
120
devcloud/mcenter/permission/checker.go
Normal file
@ -0,0 +1,120 @@
|
||||
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
|
||||
}
|
76
devcloud/mcenter/permission/design.drawio
Normal file
76
devcloud/mcenter/permission/design.drawio
Normal file
@ -0,0 +1,76 @@
|
||||
<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>
|
49
devcloud/mcenter/permission/endpoint.go
Normal file
49
devcloud/mcenter/permission/endpoint.go
Normal file
@ -0,0 +1,49 @@
|
||||
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
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user