Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
15f32e0e30 | |||
066b9f7ad2 | |||
42bb490180 | |||
c1f7ce2b85 | |||
87bda92fac | |||
ba04b02cf8 | |||
0abb789a44 | |||
c76c8b1100 | |||
1b27d97f2a | |||
13ea1f2e04 | |||
c1f263ab84 | |||
f018f28390 | |||
0d7cdec6ea | |||
8c58769937 |
2
devcloud/.vscode/launch.json
vendored
2
devcloud/.vscode/launch.json
vendored
@ -5,7 +5,7 @@
|
|||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"name": "mcenter api server",
|
"name": "devcloud api server",
|
||||||
"type": "go",
|
"type": "go",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"mode": "auto",
|
"mode": "auto",
|
||||||
|
@ -6,8 +6,8 @@ devcloud: 研发云, 给产研团队(技术团队), 产品经理, 项目经理,
|
|||||||
+ 需求管理: Jira, 禅道, ...(x)
|
+ 需求管理: Jira, 禅道, ...(x)
|
||||||
+ 应用管理: 立项后的 SCM 源代码管理, 应用的元数据, 服务树(服务分组)
|
+ 应用管理: 立项后的 SCM 源代码管理, 应用的元数据, 服务树(服务分组)
|
||||||
+ 资源管理: CMDB
|
+ 资源管理: CMDB
|
||||||
+ 应用构建: CI, 流水线发布, 应用的持续构建, Jenkins, 新一代的流程, 基于K8s Job自己设计
|
|
||||||
+ 发布中心(Dev/Test/Pre/Pro)CD: 发布, 应用维护, 部署集群的维护
|
+ 发布中心(Dev/Test/Pre/Pro)CD: 发布, 应用维护, 部署集群的维护
|
||||||
|
+ 应用构建: CI, 流水线发布, 应用的持续构建, Jenkins, 新一代的流程, 基于K8s Job自己设计
|
||||||
|
|
||||||
多业务模块组成, 渐进式微服务开发方式
|
多业务模块组成, 渐进式微服务开发方式
|
||||||
|
|
||||||
|
@ -58,7 +58,6 @@ func (i *consumer) Priority() int {
|
|||||||
return event.PRIORITY - 1
|
return event.PRIORITY - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *consumer) Close(ctx context.Context) error {
|
func (i *consumer) Close(ctx context.Context) {
|
||||||
i.ctx.Done()
|
i.ctx.Done()
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
@ -1,2 +1,14 @@
|
|||||||
# 资源管理
|
# 资源管理
|
||||||
|
|
||||||
|
## CMDB需求介绍
|
||||||
|
|
||||||
|
公司所有的资产(ITMS), 运维管理的资产(虚拟机), 为这些资产提供维护(日志清理, 安全检查, 监控告警, ...), Excel
|
||||||
|
|
||||||
|
1. 维护麻烦, 静态的, 每次变更都需要人为把信息同步给其他系统。
|
||||||
|
2. 没法通过API提供数据给其他系统, 没法做系统集成, 系统化程度低。
|
||||||
|
|
||||||
|
|
||||||
|
大的数据库,通过为这些数据提供API, 实现数据的 高速集成, 数据一定准确
|
||||||
|
|
||||||
|
## 常见的CMDB设计模式
|
||||||
|
|
||||||
|
7
devcloud/cmdb/apps/registry.go
Normal file
7
devcloud/cmdb/apps/registry.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package apps
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "122.51.31.227/go-course/go18/devcloud/cmdb/apps/resource/impl"
|
||||||
|
_ "122.51.31.227/go-course/go18/devcloud/cmdb/apps/secret/impl"
|
||||||
|
_ "122.51.31.227/go-course/go18/devcloud/cmdb/apps/secret/sync"
|
||||||
|
)
|
2
devcloud/cmdb/apps/resource/README.md
Normal file
2
devcloud/cmdb/apps/resource/README.md
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# 资源管理模块
|
||||||
|
|
1
devcloud/cmdb/apps/resource/api/api.go
Normal file
1
devcloud/cmdb/apps/resource/api/api.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package api
|
34
devcloud/cmdb/apps/resource/enum.go
Normal file
34
devcloud/cmdb/apps/resource/enum.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package resource
|
||||||
|
|
||||||
|
const (
|
||||||
|
VENDOR_ALIYUN VENDOR = "ali"
|
||||||
|
VENDOR_TENCENT VENDOR = "tx"
|
||||||
|
VENDOR_HUAWEI VENDOR = "hw"
|
||||||
|
VENDOR_VMWARE VENDOR = "vmware"
|
||||||
|
VENDOR_AWS VENDOR = "aws"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VENDOR string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// 业务资源
|
||||||
|
TYPE_VM TYPE = "vm"
|
||||||
|
TYPE_RDS TYPE = "rds"
|
||||||
|
TYPE_REDIS TYPE = "redis"
|
||||||
|
TYPE_BUCKET TYPE = "bucket"
|
||||||
|
TYPE_DISK TYPE = "disk"
|
||||||
|
TYPE_LB TYPE = "lb"
|
||||||
|
TYPE_DOMAIN TYPE = "domain"
|
||||||
|
TYPE_EIP TYPE = "eip"
|
||||||
|
TYPE_MONGODB TYPE = "mongodb"
|
||||||
|
// 子资源
|
||||||
|
TYPE_DATABASE TYPE = "database"
|
||||||
|
TYPE_ACCOUNT TYPE = "account"
|
||||||
|
// 未知资源
|
||||||
|
TYPE_OTHER TYPE = "other"
|
||||||
|
// 辅助资源
|
||||||
|
TYPE_BILL TYPE = "bill"
|
||||||
|
TYPE_ORDER TYPE = "order"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TYPE string
|
31
devcloud/cmdb/apps/resource/impl/impl.go
Normal file
31
devcloud/cmdb/apps/resource/impl/impl.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package impl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"122.51.31.227/go-course/go18/devcloud/cmdb/apps/resource"
|
||||||
|
"github.com/infraboard/mcube/v2/ioc"
|
||||||
|
"github.com/infraboard/mcube/v2/ioc/config/datasource"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
ioc.Controller().Registry(&ResourceServiceImpl{})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ resource.Service = (*ResourceServiceImpl)(nil)
|
||||||
|
|
||||||
|
type ResourceServiceImpl struct {
|
||||||
|
ioc.ObjectImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ResourceServiceImpl) Name() string {
|
||||||
|
return resource.APP_NAME
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ResourceServiceImpl) Init() error {
|
||||||
|
if datasource.Get().AutoMigrate {
|
||||||
|
err := datasource.DB().AutoMigrate(&resource.Resource{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
28
devcloud/cmdb/apps/resource/impl/resource.go
Normal file
28
devcloud/cmdb/apps/resource/impl/resource.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package impl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"122.51.31.227/go-course/go18/devcloud/cmdb/apps/resource"
|
||||||
|
"github.com/infraboard/mcube/v2/ioc/config/datasource"
|
||||||
|
"github.com/infraboard/mcube/v2/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Add implements resource.Service.
|
||||||
|
func (s *ResourceServiceImpl) Add(ctx context.Context, in *resource.Resource) (*resource.Resource, error) {
|
||||||
|
if err := datasource.DBFromCtx(ctx).Save(in).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return in, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteResource implements resource.Service.
|
||||||
|
func (s *ResourceServiceImpl) DeleteResource(ctx context.Context, in *resource.DeleteResourceRequest) error {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search implements resource.Service.
|
||||||
|
func (s *ResourceServiceImpl) Search(ctx context.Context, in *resource.SearchRequest) (*types.Set[*resource.Resource], error) {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
60
devcloud/cmdb/apps/resource/interface.go
Normal file
60
devcloud/cmdb/apps/resource/interface.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package resource
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/infraboard/mcube/v2/http/request"
|
||||||
|
"github.com/infraboard/mcube/v2/ioc"
|
||||||
|
"github.com/infraboard/mcube/v2/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
APP_NAME = "resource"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetService() Service {
|
||||||
|
return ioc.Controller().Get(APP_NAME).(Service)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Service interface {
|
||||||
|
// 资源搜索
|
||||||
|
Search(context.Context, *SearchRequest) (*types.Set[*Resource], error)
|
||||||
|
// Save 更新与创建同时
|
||||||
|
Add(context.Context, *Resource) (*Resource, error)
|
||||||
|
// 删除
|
||||||
|
DeleteResource(context.Context, *DeleteResourceRequest) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSearchRequest().SetType(”).SetXXX(v)
|
||||||
|
// (WithOptz(), WithOptx(), WithOpty()...)
|
||||||
|
func NewSearchRequest() *SearchRequest {
|
||||||
|
return &SearchRequest{
|
||||||
|
PageRequest: request.NewDefaultPageRequest(),
|
||||||
|
Tags: map[string]string{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type SearchRequest struct {
|
||||||
|
// 分页请求
|
||||||
|
*request.PageRequest
|
||||||
|
// 名称做模糊搜索
|
||||||
|
Keywords string `json:"keywords"`
|
||||||
|
// 类型
|
||||||
|
Type *TYPE `json:"type"`
|
||||||
|
// 标签
|
||||||
|
Tags map[string]string `json:"lable"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SearchRequest) SetType(t TYPE) *SearchRequest {
|
||||||
|
r.Type = &t
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDeleteResourceRequest() *DeleteResourceRequest {
|
||||||
|
return &DeleteResourceRequest{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 支持多个
|
||||||
|
type DeleteResourceRequest struct {
|
||||||
|
ResourceIds []string `json:"resource_ids"`
|
||||||
|
}
|
130
devcloud/cmdb/apps/resource/model.go
Normal file
130
devcloud/cmdb/apps/resource/model.go
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
package resource
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/infraboard/mcube/v2/tools/pretty"
|
||||||
|
"github.com/infraboard/mcube/v2/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewResourceSet() *types.Set[*Resource] {
|
||||||
|
return types.New[*Resource]()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewResource() *Resource {
|
||||||
|
return &Resource{
|
||||||
|
Meta: Meta{},
|
||||||
|
Spec: Spec{
|
||||||
|
Tags: map[string]string{},
|
||||||
|
Extra: map[string]string{},
|
||||||
|
},
|
||||||
|
Status: Status{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 资源
|
||||||
|
// https://www.mongodb.com/docs/drivers/go/current/fundamentals/bson/#struct-tags
|
||||||
|
type Resource struct {
|
||||||
|
Meta `bson:"inline"`
|
||||||
|
Spec `bson:"inline"`
|
||||||
|
Status `bson:"inline"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resource) String() string {
|
||||||
|
return pretty.ToJSON(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resource) TableName() string {
|
||||||
|
return "resources"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 元数据,不会变的
|
||||||
|
type Meta struct {
|
||||||
|
// 全局唯一Id, 直接使用个云商自己的Id
|
||||||
|
Id string `bson:"_id" json:"id" validate:"required" gorm:"column:id;primaryKey"`
|
||||||
|
// 资源所属域
|
||||||
|
Domain string `json:"domain" validate:"required" gorm:"column:domain"`
|
||||||
|
// 资源所属空间
|
||||||
|
Namespace string `json:"namespace" validate:"required" gorm:"column:namespace"`
|
||||||
|
// 资源所属环境
|
||||||
|
Env string `json:"env" gorm:"column:env"`
|
||||||
|
// 创建时间
|
||||||
|
CreateAt int64 `json:"create_at" gorm:"column:create_at"`
|
||||||
|
// 删除时间
|
||||||
|
DeteteAt int64 `json:"detete_at" gorm:"column:detete_at"`
|
||||||
|
// 删除人
|
||||||
|
DeteteBy string `json:"detete_by" gorm:"column:detete_by"`
|
||||||
|
// 同步时间
|
||||||
|
SyncAt int64 `json:"sync_at" validate:"required" gorm:"column:sync_at"`
|
||||||
|
// 同步人
|
||||||
|
SyncBy string `json:"sync_by" gorm:"column:sync_by"`
|
||||||
|
// 用于同步的凭证ID
|
||||||
|
CredentialId string `json:"credential_id" gorm:"column:credential_id"`
|
||||||
|
// 序列号
|
||||||
|
SerialNumber string `json:"serial_number" gorm:"column:serial_number"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表单数据, 用户申请资源时 需要提供的参数
|
||||||
|
type Spec struct {
|
||||||
|
// 厂商
|
||||||
|
Vendor VENDOR `json:"vendor" gorm:"column:vendor"`
|
||||||
|
// 资源类型
|
||||||
|
ResourceType TYPE `json:"resource_type" gorm:"column:resource_type"`
|
||||||
|
// 地域
|
||||||
|
Region string `json:"region" gorm:"column:region"`
|
||||||
|
// 区域
|
||||||
|
Zone string `json:"zone" gorm:"column:zone"`
|
||||||
|
// 资源所属账号
|
||||||
|
Owner string `json:"owner" gorm:"column:owner"`
|
||||||
|
// 名称
|
||||||
|
Name string `json:"name" gorm:"column:name"`
|
||||||
|
// 种类
|
||||||
|
Category string `json:"category" gorm:"column:category"`
|
||||||
|
// 规格
|
||||||
|
Type string `json:"type" gorm:"column:type"`
|
||||||
|
// 描述
|
||||||
|
Description string `json:"description" gorm:"column:description"`
|
||||||
|
// 过期时间
|
||||||
|
ExpireAt int64 `json:"expire_at" gorm:"column:expire_at"`
|
||||||
|
// 更新时间
|
||||||
|
UpdateAt int64 `json:"update_at" gorm:"column:update_at"`
|
||||||
|
// 资源占用Cpu数量
|
||||||
|
Cpu int64 `json:"cpu" gorm:"column:cpu"`
|
||||||
|
// GPU数量
|
||||||
|
Gpu int64 `json:"gpu" gorm:"column:gpu"`
|
||||||
|
// 资源使用的内存
|
||||||
|
Memory int64 `json:"memory" gorm:"column:memory"`
|
||||||
|
// 系统盘
|
||||||
|
SystemStorage int64 `json:"system_storage" gorm:"column:system_storage"`
|
||||||
|
// 数据盘
|
||||||
|
DataStorage int64 `json:"data_storage" gorm:"column:data_storage"`
|
||||||
|
// 公网IP带宽, 单位M
|
||||||
|
BandWidth int32 `json:"band_width" gorm:"column:band_width"`
|
||||||
|
// 资源标签
|
||||||
|
Tags map[string]string `json:"tags" gorm:"column:tags;serializer:json;type:json"`
|
||||||
|
// 额外的通用属性
|
||||||
|
Extra map[string]string `json:"extra" gorm:"column:extra;serializer:json;type:json"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 资源当前状态
|
||||||
|
type Status struct {
|
||||||
|
// 资源当前状态
|
||||||
|
Phase string `json:"phase" gorm:"column:phase"`
|
||||||
|
// 资源当前状态描述
|
||||||
|
Describe string `json:"describe" gorm:"column:describe"`
|
||||||
|
// 实例锁定模式; Unlock:正常;ManualLock:手动触发锁定;LockByExpiration:实例过期自动锁定;LockByRestoration:实例回滚前的自动锁定;LockByDiskQuota:实例空间满自动锁定
|
||||||
|
LockMode string `json:"lock_mode" gorm:"column:lock_mode"`
|
||||||
|
// 锁定原因
|
||||||
|
LockReason string `json:"lock_reason" gorm:"column:lock_reason"`
|
||||||
|
// 资源访问地址
|
||||||
|
// 公网地址, 或者域名
|
||||||
|
PublicAddress []string `json:"public_address" gorm:"column:public_address;serializer:json;type:json"`
|
||||||
|
// 内网地址, 或者域名
|
||||||
|
PrivateAddress []string `json:"private_address" gorm:"column:private_address;serializer:json;type:json"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Status) GetFirstPrivateAddress() string {
|
||||||
|
if len(s.PrivateAddress) > 0 {
|
||||||
|
return s.PrivateAddress[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
2
devcloud/cmdb/apps/secret/README.md
Normal file
2
devcloud/cmdb/apps/secret/README.md
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# 资源凭证管理
|
||||||
|
|
1
devcloud/cmdb/apps/secret/api/api.go
Normal file
1
devcloud/cmdb/apps/secret/api/api.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package api
|
5
devcloud/cmdb/apps/secret/const.go
Normal file
5
devcloud/cmdb/apps/secret/const.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package secret
|
||||||
|
|
||||||
|
const (
|
||||||
|
TASK_LABLE_SECRET_ID = "secret_id"
|
||||||
|
)
|
31
devcloud/cmdb/apps/secret/impl/impl.go
Normal file
31
devcloud/cmdb/apps/secret/impl/impl.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package impl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"122.51.31.227/go-course/go18/devcloud/cmdb/apps/secret"
|
||||||
|
"github.com/infraboard/mcube/v2/ioc"
|
||||||
|
"github.com/infraboard/mcube/v2/ioc/config/datasource"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
ioc.Controller().Registry(&SecretServiceImpl{})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ secret.Service = (*SecretServiceImpl)(nil)
|
||||||
|
|
||||||
|
type SecretServiceImpl struct {
|
||||||
|
ioc.ObjectImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SecretServiceImpl) Name() string {
|
||||||
|
return secret.APP_NAME
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SecretServiceImpl) Init() error {
|
||||||
|
if datasource.Get().AutoMigrate {
|
||||||
|
err := datasource.DB().AutoMigrate(&secret.Secret{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
15
devcloud/cmdb/apps/secret/impl/impl_test.go
Normal file
15
devcloud/cmdb/apps/secret/impl/impl_test.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package impl_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"122.51.31.227/go-course/go18/devcloud/cmdb/apps/secret"
|
||||||
|
"122.51.31.227/go-course/go18/devcloud/cmdb/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
svc secret.Service
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
test.DevelopmentSetUp()
|
||||||
|
svc = secret.GetService()
|
||||||
|
}
|
100
devcloud/cmdb/apps/secret/impl/secret.go
Normal file
100
devcloud/cmdb/apps/secret/impl/secret.go
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
package impl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"122.51.31.227/go-course/go18/devcloud/cmdb/apps/secret"
|
||||||
|
"github.com/infraboard/mcube/v2/exception"
|
||||||
|
"github.com/infraboard/mcube/v2/ioc/config/datasource"
|
||||||
|
"github.com/infraboard/mcube/v2/types"
|
||||||
|
"github.com/infraboard/modules/task/apps/task"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateSecret implements secret.Service.
|
||||||
|
func (s *SecretServiceImpl) CreateSecret(ctx context.Context, in *secret.CreateSecretRequest) (*secret.Secret, error) {
|
||||||
|
ins := secret.NewSecret(in)
|
||||||
|
|
||||||
|
// 需要加密
|
||||||
|
if err := ins.EncryptedApiSecret(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// upsert, gorm save
|
||||||
|
if err := datasource.DBFromCtx(ctx).Save(ins).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ins, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DescribeSecret implements secret.Service.
|
||||||
|
func (s *SecretServiceImpl) DescribeSecret(ctx context.Context, in *secret.DescribeSecretRequeset) (*secret.Secret, error) {
|
||||||
|
// 取出后,需要解密
|
||||||
|
ins := &secret.Secret{}
|
||||||
|
query := in.GormResourceFilter(datasource.DBFromCtx(ctx).Model(&secret.Secret{}))
|
||||||
|
if err := query.Where("id = ?", in.Id).Take(ins).Error; err != nil {
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
return nil, exception.NewNotFound("secret %s not found", in.Id)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ins.SetIsEncrypted(true)
|
||||||
|
if err := ins.DecryptedApiSecret(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解密过后的数据
|
||||||
|
return ins, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// QuerySecret implements secret.Service.
|
||||||
|
func (s *SecretServiceImpl) QuerySecret(ctx context.Context, in *secret.QuerySecretRequest) (*types.Set[*secret.Secret], error) {
|
||||||
|
set := types.New[*secret.Secret]()
|
||||||
|
|
||||||
|
query := in.GormResourceFilter(datasource.DBFromCtx(ctx).Model(&secret.Secret{}))
|
||||||
|
err := query.Count(&set.Total).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = query.
|
||||||
|
Order("create_at desc").
|
||||||
|
Offset(int(in.ComputeOffset())).
|
||||||
|
Limit(int(in.PageSize)).
|
||||||
|
Find(&set.Items).
|
||||||
|
Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return set, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyncResource implements secret.Service.
|
||||||
|
// 资源同步接口
|
||||||
|
func (s *SecretServiceImpl) SyncResource(ctx context.Context, in *secret.SyncResourceRequest) (*types.Set[*task.Task], error) {
|
||||||
|
// 直接初始化并返回,避免警告
|
||||||
|
taskSet := types.NewSet[*task.Task]()
|
||||||
|
|
||||||
|
// 查询Secret信息
|
||||||
|
ins, err := s.DescribeSecret(ctx, secret.NewDescribeSecretRequeset(in.Id))
|
||||||
|
if err != nil {
|
||||||
|
return taskSet, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ins.GetEnabled() {
|
||||||
|
return taskSet, fmt.Errorf("secret %s not enabled", ins.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取syncer, 执行同步
|
||||||
|
for _, rs := range ins.ResourceType {
|
||||||
|
syncer := secret.GetSyncer(rs)
|
||||||
|
for _, region := range ins.Regions {
|
||||||
|
taskInfo := syncer.Sync(ctx, ins, region, rs)
|
||||||
|
taskSet.Add(taskInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return taskSet, nil
|
||||||
|
}
|
59
devcloud/cmdb/apps/secret/impl/secret_test.go
Normal file
59
devcloud/cmdb/apps/secret/impl/secret_test.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package impl_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"122.51.31.227/go-course/go18/devcloud/cmdb/apps/resource"
|
||||||
|
"122.51.31.227/go-course/go18/devcloud/cmdb/apps/secret"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateSecret(t *testing.T) {
|
||||||
|
req := secret.NewCreateSecretRequest()
|
||||||
|
req.Name = "腾讯云只读账号"
|
||||||
|
req.Vendor = resource.VENDOR_TENCENT
|
||||||
|
req.ApiKey = os.Getenv("TX_API_KEY")
|
||||||
|
req.ApiSecret = os.Getenv("TX_API_SECRET")
|
||||||
|
req.SetEnabled(true)
|
||||||
|
req.ResourceType = append(req.ResourceType, resource.TYPE_VM)
|
||||||
|
req.Regions = []string{"ap-shanghai", "ap-guangzhou"}
|
||||||
|
ins, err := svc.CreateSecret(t.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(ins)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuerySecret(t *testing.T) {
|
||||||
|
req := secret.NewQuerySecretRequest()
|
||||||
|
set, err := svc.QuerySecret(t.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(set)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
SECRET_ID = "e5aa1ad4-6069-397e-a7ed-a78b9cd2a93b"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDescribeSecret(t *testing.T) {
|
||||||
|
req := secret.NewDescribeSecretRequeset(SECRET_ID)
|
||||||
|
ins, err := svc.DescribeSecret(t.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(ins)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSyncResource(t *testing.T) {
|
||||||
|
req := secret.NewSyncResourceRequest(SECRET_ID)
|
||||||
|
ins, err := svc.SyncResource(t.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(ins)
|
||||||
|
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
}
|
97
devcloud/cmdb/apps/secret/interface.go
Normal file
97
devcloud/cmdb/apps/secret/interface.go
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
package secret
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"122.51.31.227/go-course/go18/devcloud/cmdb/apps/resource"
|
||||||
|
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/policy"
|
||||||
|
"github.com/infraboard/mcube/v2/http/request"
|
||||||
|
"github.com/infraboard/mcube/v2/ioc"
|
||||||
|
"github.com/infraboard/mcube/v2/tools/pretty"
|
||||||
|
"github.com/infraboard/mcube/v2/types"
|
||||||
|
"github.com/infraboard/modules/task/apps/task"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
APP_NAME = "secret"
|
||||||
|
SECRET_KEY = "23gs6gxHrz1kNEvshRmunkXbwIiaEcYfh+EMu+e9ewA="
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetService() Service {
|
||||||
|
return ioc.Controller().Get(APP_NAME).(Service)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Service interface {
|
||||||
|
// 用于Secret的管理(后台管理员配置)
|
||||||
|
// 创建secret
|
||||||
|
CreateSecret(context.Context, *CreateSecretRequest) (*Secret, error)
|
||||||
|
// 查询secret
|
||||||
|
QuerySecret(context.Context, *QuerySecretRequest) (*types.Set[*Secret], error)
|
||||||
|
// 查询详情, 已解密, API层需要脱敏
|
||||||
|
DescribeSecret(context.Context, *DescribeSecretRequeset) (*Secret, error)
|
||||||
|
|
||||||
|
// 基于云商凭证来同步资源
|
||||||
|
// 怎么API怎么设计
|
||||||
|
// 同步阿里云所有资源, 10分钟,30分钟 ...
|
||||||
|
// 这个接口调用持续30分钟...
|
||||||
|
// 需要拆解为异步任务: 用户调用了同步后, 里面返回, 这个同步任务在后台执行(Gorouties), 需要查询同步日志(Ws)
|
||||||
|
|
||||||
|
// 使用task模块来执行异步任务, 通过TaskId查询异步任务状态
|
||||||
|
SyncResource(context.Context, *SyncResourceRequest) (*types.Set[*task.Task], error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同步记录(task) 有状态
|
||||||
|
type SyncResourceTask struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type QuerySyncLogRequest struct {
|
||||||
|
TaskId int `json:"task_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 每个资源的同步日志
|
||||||
|
type SyncRecord struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResourceResponse struct {
|
||||||
|
Success bool
|
||||||
|
InstanceId string `json:"instance_id"`
|
||||||
|
Resource *resource.Resource `json:"resource"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t ResourceResponse) String() string {
|
||||||
|
return pretty.ToJSON(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewQuerySecretRequest() *QuerySecretRequest {
|
||||||
|
return &QuerySecretRequest{
|
||||||
|
PageRequest: request.NewDefaultPageRequest(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type QuerySecretRequest struct {
|
||||||
|
policy.ResourceScope
|
||||||
|
// 分页请求
|
||||||
|
*request.PageRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDescribeSecretRequeset(id string) *DescribeSecretRequeset {
|
||||||
|
return &DescribeSecretRequeset{
|
||||||
|
Id: id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type DescribeSecretRequeset struct {
|
||||||
|
policy.ResourceScope
|
||||||
|
Id string `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSyncResourceRequest(id string) *SyncResourceRequest {
|
||||||
|
return &SyncResourceRequest{
|
||||||
|
Id: id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type SyncResourceRequest struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
}
|
152
devcloud/cmdb/apps/secret/model.go
Normal file
152
devcloud/cmdb/apps/secret/model.go
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
package secret
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"122.51.31.227/go-course/go18/devcloud/cmdb/apps/resource"
|
||||||
|
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/policy"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/infraboard/devops/pkg/model"
|
||||||
|
"github.com/infraboard/mcube/v2/crypto/cbc"
|
||||||
|
"github.com/infraboard/mcube/v2/tools/pretty"
|
||||||
|
"github.com/infraboard/mcube/v2/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewSecretSet() *types.Set[*Secret] {
|
||||||
|
return types.New[*Secret]()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSecret(in *CreateSecretRequest) *Secret {
|
||||||
|
// hash版本的UUID
|
||||||
|
// Vendor Address ApiKey
|
||||||
|
uid := uuid.NewMD5(uuid.Nil, fmt.Appendf(nil, "%s.%s.%s", in.Vendor, in.Address, in.ApiKey)).String()
|
||||||
|
return &Secret{
|
||||||
|
Meta: *model.NewMeta().SetId(uid),
|
||||||
|
CreateSecretRequest: *in,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Secret struct {
|
||||||
|
model.Meta
|
||||||
|
// 资源范围, Namespace是继承的, Scope是API添加的
|
||||||
|
policy.ResourceLabel
|
||||||
|
// 资源定义
|
||||||
|
CreateSecretRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Secret) TableName() string {
|
||||||
|
return "secrets"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Secret) SetDefault() *Secret {
|
||||||
|
if s.SyncLimit == 0 {
|
||||||
|
s.SyncLimit = 10
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Secret) String() string {
|
||||||
|
return pretty.ToJSON(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCreateSecretRequest() *CreateSecretRequest {
|
||||||
|
return &CreateSecretRequest{
|
||||||
|
Regions: []string{},
|
||||||
|
SyncLimit: 10,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateSecretRequest struct {
|
||||||
|
// 是否启用
|
||||||
|
Enabled *bool `json:"enabled" gorm:"column:enabled"`
|
||||||
|
// 名称
|
||||||
|
Name string `json:"name" gorm:"column:name"`
|
||||||
|
// 尝试
|
||||||
|
Vendor resource.VENDOR `json:"vendor" gorm:"column:vendor"`
|
||||||
|
// Vmware
|
||||||
|
Address string `json:"address" gorm:"column:address"`
|
||||||
|
// 需要被脱敏
|
||||||
|
// Musk
|
||||||
|
ApiKey string `json:"api_key" gorm:"column:api_key"`
|
||||||
|
// ApiKey
|
||||||
|
ApiSecret string `json:"api_secret" mask:",5,4" gorm:"column:api_secret"`
|
||||||
|
// 资源所在区域
|
||||||
|
Regions []string `json:"regions" gorm:"column:regions;serializer:json;type:json"`
|
||||||
|
// 资源列表
|
||||||
|
ResourceType []resource.TYPE `json:"resource_type" gorm:"column:resource_type;serializer:json;type:json"`
|
||||||
|
// 同步分页大小
|
||||||
|
SyncLimit int64 `json:"sync_limit" gorm:"column:sync_limit"`
|
||||||
|
|
||||||
|
//
|
||||||
|
isEncrypted bool `gorm:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *CreateSecretRequest) SetIsEncrypted(v bool) *CreateSecretRequest {
|
||||||
|
r.isEncrypted = v
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *CreateSecretRequest) SetEnabled(v bool) *CreateSecretRequest {
|
||||||
|
r.Enabled = &v
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *CreateSecretRequest) GetEnabled() bool {
|
||||||
|
if r.Enabled == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return *r.Enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *CreateSecretRequest) GetSyncLimit() int64 {
|
||||||
|
if r.SyncLimit == 0 {
|
||||||
|
return 10
|
||||||
|
}
|
||||||
|
return r.SyncLimit
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *CreateSecretRequest) EncryptedApiSecret() error {
|
||||||
|
if r.isEncrypted {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Hash, 对称,非对称
|
||||||
|
// 对称加密 AES(cbc)
|
||||||
|
// @v1,xxxx@xxxxx
|
||||||
|
|
||||||
|
key, err := base64.StdEncoding.DecodeString(SECRET_KEY)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cipherText, err := cbc.MustNewAESCBCCihper(key).Encrypt([]byte(r.ApiSecret))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.ApiSecret = base64.StdEncoding.EncodeToString(cipherText)
|
||||||
|
r.SetIsEncrypted(true)
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *CreateSecretRequest) DecryptedApiSecret() error {
|
||||||
|
if r.isEncrypted {
|
||||||
|
cipherdText, err := base64.StdEncoding.DecodeString(r.ApiSecret)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
key, err := base64.StdEncoding.DecodeString(SECRET_KEY)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
plainText, err := cbc.MustNewAESCBCCihper(key).Decrypt([]byte(cipherdText))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.ApiSecret = string(plainText)
|
||||||
|
r.SetIsEncrypted(false)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
34
devcloud/cmdb/apps/secret/sync.go
Normal file
34
devcloud/cmdb/apps/secret/sync.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package secret
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"122.51.31.227/go-course/go18/devcloud/cmdb/apps/resource"
|
||||||
|
"github.com/infraboard/modules/task/apps/task"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
SYNCER_PREFIX = "syncer"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
syncers = map[string]Syncer{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetSyncerName(t resource.TYPE) string {
|
||||||
|
return fmt.Sprintf("%s_%s", SYNCER_PREFIX, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSyncer(t resource.TYPE) Syncer {
|
||||||
|
return syncers[fmt.Sprintf("%s_%s", SYNCER_PREFIX, t)]
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegistrySyncer(t resource.TYPE, s Syncer) {
|
||||||
|
syncers[fmt.Sprintf("%s_%s", SYNCER_PREFIX, t)] = s
|
||||||
|
}
|
||||||
|
|
||||||
|
type Syncer interface {
|
||||||
|
// 资源同步
|
||||||
|
Sync(ctx context.Context, ins *Secret, region string, rt resource.TYPE) *task.Task
|
||||||
|
}
|
59
devcloud/cmdb/apps/secret/sync/data/vm.json
Normal file
59
devcloud/cmdb/apps/secret/sync/data/vm.json
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
{
|
||||||
|
"Response": {
|
||||||
|
"InstanceSet": [
|
||||||
|
{
|
||||||
|
"BlueprintId": "lhbp-b46k6f98",
|
||||||
|
"BundleId": "bundle_starter_mc_promo_med2_02",
|
||||||
|
"CPU": 2,
|
||||||
|
"CreatedTime": "2025-03-02T09:54:10Z",
|
||||||
|
"ExpiredTime": "2026-03-02T09:54:10Z",
|
||||||
|
"InitInvocationId": "",
|
||||||
|
"InstanceChargeType": "PREPAID",
|
||||||
|
"InstanceId": "lhins-dxvcse1s",
|
||||||
|
"InstanceName": "Docker CE-MyRb",
|
||||||
|
"InstanceRestrictState": "NORMAL",
|
||||||
|
"InstanceState": "RUNNING",
|
||||||
|
"InternetAccessible": {
|
||||||
|
"InternetChargeType": "TRAFFIC_POSTPAID_BY_HOUR",
|
||||||
|
"InternetMaxBandwidthOut": 4,
|
||||||
|
"PublicIpAssigned": true
|
||||||
|
},
|
||||||
|
"IsolatedTime": null,
|
||||||
|
"LatestOperation": "",
|
||||||
|
"LatestOperationRequestId": "",
|
||||||
|
"LatestOperationStartedTime": "2025-03-02T09:53:37Z",
|
||||||
|
"LatestOperationState": "",
|
||||||
|
"LoginSettings": {
|
||||||
|
"KeyIds": []
|
||||||
|
},
|
||||||
|
"Memory": 2,
|
||||||
|
"OsName": "Ubuntu Server 24.04 LTS 64bit",
|
||||||
|
"Platform": "UBUNTU",
|
||||||
|
"PlatformType": "LINUX_UNIX",
|
||||||
|
"PrivateAddresses": [
|
||||||
|
"10.0.4.9"
|
||||||
|
],
|
||||||
|
"PublicAddresses": [
|
||||||
|
"122.51.31.227"
|
||||||
|
],
|
||||||
|
"PublicIpv6Addresses": [],
|
||||||
|
"RenewFlag": "NOTIFY_AND_MANUAL_RENEW",
|
||||||
|
"SupportIpv6Detail": {
|
||||||
|
"Detail": "EFFECTIVE_IMMEDIATELY",
|
||||||
|
"IsSupport": true,
|
||||||
|
"Message": ""
|
||||||
|
},
|
||||||
|
"SystemDisk": {
|
||||||
|
"DiskId": "lhdisk-adyw1bgo",
|
||||||
|
"DiskSize": 50,
|
||||||
|
"DiskType": "CLOUD_SSD"
|
||||||
|
},
|
||||||
|
"Tags": [],
|
||||||
|
"Uuid": "dd452294-6c06-4d4e-ab5f-b5d74404b863",
|
||||||
|
"Zone": "ap-shanghai-2"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"RequestId": "9863afd3-47f4-4aa0-9e95-9b61ea3e5573",
|
||||||
|
"TotalCount": 1
|
||||||
|
}
|
||||||
|
}
|
116
devcloud/cmdb/apps/secret/sync/vm.go
Normal file
116
devcloud/cmdb/apps/secret/sync/vm.go
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
package sync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"122.51.31.227/go-course/go18/devcloud/cmdb/apps/resource"
|
||||||
|
"122.51.31.227/go-course/go18/devcloud/cmdb/apps/secret"
|
||||||
|
"github.com/infraboard/mcube/v2/ioc"
|
||||||
|
"github.com/infraboard/mcube/v2/ioc/config/log"
|
||||||
|
"github.com/infraboard/mcube/v2/tools/ptr"
|
||||||
|
"github.com/infraboard/modules/task/apps/event"
|
||||||
|
"github.com/infraboard/modules/task/apps/task"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
|
||||||
|
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
|
||||||
|
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
|
||||||
|
lighthouse "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/lighthouse/v20200324"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
ioc.Controller().Registry(&VmSyncerServiceImpl{})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ secret.Syncer = (*VmSyncerServiceImpl)(nil)
|
||||||
|
|
||||||
|
type VmSyncerServiceImpl struct {
|
||||||
|
ioc.ObjectImpl
|
||||||
|
|
||||||
|
log *zerolog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *VmSyncerServiceImpl) Name() string {
|
||||||
|
return secret.GetSyncerName(resource.TYPE_VM)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *VmSyncerServiceImpl) Init() error {
|
||||||
|
s.log = log.Sub(s.Name())
|
||||||
|
secret.RegistrySyncer(resource.TYPE_VM, s)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync implements secret.Syncer.
|
||||||
|
func (s *VmSyncerServiceImpl) Sync(ctx context.Context, ins *secret.Secret, region string, rs resource.TYPE) *task.Task {
|
||||||
|
// 怎么使用对应secret 来完成, 用的腾讯的API
|
||||||
|
// https://console.cloud.tencent.com/api/explorer?Product=cvm&Version=2017-03-12&Action=DescribeRegions
|
||||||
|
// https://console.cloud.tencent.com/api/explorer?Product=lighthouse&Version=2020-03-24&Action=DescribeInstances
|
||||||
|
return task.GetService().Run(ctx, task.NewFnTask(func(ctx context.Context, req any) error {
|
||||||
|
taskInfo := task.GetTaskFromCtx(ctx)
|
||||||
|
_, err := event.GetService().AddEvent(ctx, task.NewInfoEvent("同步开始", taskInfo.Id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer event.GetService().AddEvent(ctx, task.NewInfoEvent("同步结束", taskInfo.Id))
|
||||||
|
|
||||||
|
// 实例化一个认证对象,入参需要传入腾讯云账户 SecretId 和 SecretKey,此处还需注意密钥对的保密
|
||||||
|
// 代码泄露可能会导致 SecretId 和 SecretKey 泄露,并威胁账号下所有资源的安全性
|
||||||
|
// 以下代码示例仅供参考,建议采用更安全的方式来使用密钥
|
||||||
|
// 请参见:https://cloud.tencent.com/document/product/1278/85305
|
||||||
|
// 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取
|
||||||
|
credential := common.NewCredential(
|
||||||
|
ins.ApiKey,
|
||||||
|
ins.ApiSecret,
|
||||||
|
)
|
||||||
|
// 使用临时密钥示例
|
||||||
|
// credential := common.NewTokenCredential("SecretId", "SecretKey", "Token")
|
||||||
|
// 实例化一个client选项,可选的,没有特殊需求可以跳过
|
||||||
|
cpf := profile.NewClientProfile()
|
||||||
|
cpf.HttpProfile.Endpoint = "lighthouse.tencentcloudapi.com"
|
||||||
|
// 实例化要请求产品的client对象,clientProfile是可选的
|
||||||
|
// "ap-shanghai"
|
||||||
|
client, _ := lighthouse.NewClient(credential, region, cpf)
|
||||||
|
|
||||||
|
// 实例化一个请求对象,每个接口都会对应一个request对象
|
||||||
|
request := lighthouse.NewDescribeInstancesRequest()
|
||||||
|
|
||||||
|
// 返回的resp是一个DescribeInstancesResponse的实例,与请求对象对应
|
||||||
|
response, err := client.DescribeInstances(request)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = event.GetService().AddEvent(ctx, task.NewInfoEvent("接口调用成功", taskInfo.Id).SetDetail(response.ToJsonString()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取出了实例的列表
|
||||||
|
for _, vm := range response.Response.InstanceSet {
|
||||||
|
// 转化为一个Resource对象
|
||||||
|
res := resource.NewResource()
|
||||||
|
res.Vendor = resource.VENDOR_TENCENT
|
||||||
|
res.CredentialId = ins.Id
|
||||||
|
res.Region = region
|
||||||
|
res.ResourceType = resource.TYPE_VM
|
||||||
|
res.Id = ptr.GetValue(vm.InstanceId)
|
||||||
|
res.Name = ptr.GetValue(vm.InstanceName)
|
||||||
|
res.Cpu = ptr.GetValue(vm.CPU)
|
||||||
|
res.Memory = ptr.GetValue(vm.Memory)
|
||||||
|
res.SystemStorage = ptr.GetValue(vm.SystemDisk.DiskSize)
|
||||||
|
res.Extra["os_name"] = ptr.GetValue(vm.OsName)
|
||||||
|
res.PrivateAddress = ptr.GetArrayValue(vm.PrivateAddresses)
|
||||||
|
res.PublicAddress = ptr.GetArrayValue(vm.PublicAddresses)
|
||||||
|
res.Phase = ptr.GetValue(vm.InstanceState)
|
||||||
|
res, err := resource.GetService().Add(ctx, res)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = event.GetService().AddEvent(ctx, task.NewInfoEvent(fmt.Sprintf("资源%s同步成功", res.Name), taskInfo.Id).SetDetail(res.String()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}, ins).SetAsync(true).SetLabel(secret.TASK_LABLE_SECRET_ID, ins.Id))
|
||||||
|
}
|
68
devcloud/cmdb/design.drawio
Normal file
68
devcloud/cmdb/design.drawio
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
<mxfile host="65bd71144e">
|
||||||
|
<diagram id="_dFAOaWQpwMIgqf1TTqB" name="第 1 页">
|
||||||
|
<mxGraphModel dx="888" dy="495" 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="3" value="<h1 style="margin-top: 0px;">基于模型</h1><p>这里的模型,数据库里面的一种表</p><p>基于关系性数据库,设计 表概念</p><p>1. 传统: 数据库里面添加表</p><p>2.&nbsp; 通过cmdb提供的API来添加模型</p><p><br></p><p><br></p>" style="text;html=1;whiteSpace=wrap;overflow=hidden;rounded=0;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="30" y="70" width="200" height="150" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="6" 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="4" target="5">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="7" value="1: N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="6">
|
||||||
|
<mxGeometry x="0.0067" y="-2" relative="1" as="geometry">
|
||||||
|
<mxPoint as="offset"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="4" value="model表<div><br></div><div>模型的名称(ec2)</div>" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="50" y="250" width="310" height="150" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="10" style="edgeStyle=none;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" edge="1" parent="1" source="5" target="8">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="11" value="1: N" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="10">
|
||||||
|
<mxGeometry x="-0.2893" y="-1" relative="1" as="geometry">
|
||||||
|
<mxPoint as="offset"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="5" value="model 字段表<div><br></div><div>模型的字段</div><div>(name: 名称, ip: 主机的IP)</div>" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="460" y="250" width="310" height="150" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="8" value="实例表:<div>核心 记录 模型字段对应的数据</div><div>name字段编号(1): ec2_01</div><div>ip的字段编号(2): 10.10.10.1&nbsp;</div>" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="460" y="510" width="310" height="150" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="9" value="<div><br></div>ec2实例[] {<div><font color="#000000">name: ec2_01</font></div><div><font color="#000000">ip: 10.10.10.1</font></div><div><font color="#000000"><br></font><div>}</div></div>" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="60" y="510" width="170" height="90" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="12" value="<h1 style="margin-top: 0px;">基于表的CMDB</h1><p><br></p><p>沿用关系性数据库的设计逻辑, 已有的数据库工具</p><p>1. 提供一组API来让用户录入数控,并且通过API查询数据库</p><p><br></p><p><br></p>" style="text;html=1;whiteSpace=wrap;overflow=hidden;rounded=0;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="70" y="690" width="410" height="130" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="13" value="资源表<div><br></div><div>通用字段,&nbsp; 通过直接添加, 非通用字段, 直接存储为json</div>" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="60" y="870" width="310" height="150" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="14" value="Resource: CRUD的API" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="60" y="840" width="110" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="15" value="<div>凭证管理</div><div><br></div><div>存储敏感数据的表(对称加解密)</div>" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="60" y="1110" width="310" height="150" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="16" value="Secret: CRUD API" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="60" y="1080" width="110" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="17" value="<h1 style="margin-top: 0px;">敏感数据</h1><p><br></p><p><span style="background-color: transparent;">1.&nbsp; 存储加密, 访问运维看到数据库</span></p><p><span style="background-color: transparent;">1.&nbsp; API 提供的数据 需要脱名,这种数据是不允许通过API对外开放的, 只允许进程内使用</span></p><p><br></p><p><br></p>" style="text;html=1;whiteSpace=wrap;overflow=hidden;rounded=0;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="400" y="1100" width="410" height="160" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="18" value="sync" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="240" y="1220" width="120" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="19" value="secret" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="80" y="1220" width="120" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="20" style="edgeStyle=none;html=1;exitX=0.75;exitY=0;exitDx=0;exitDy=0;entryX=0.869;entryY=0.984;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="18" target="13">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
</mxfile>
|
21
devcloud/cmdb/test/setup.go
Normal file
21
devcloud/cmdb/test/setup.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/infraboard/mcube/v2/ioc"
|
||||||
|
// 要注册哪些对象, Book, Comment
|
||||||
|
|
||||||
|
// 加载的业务对象
|
||||||
|
_ "122.51.31.227/go-course/go18/devcloud/cmdb/apps"
|
||||||
|
// task模块
|
||||||
|
_ "github.com/infraboard/modules/task/apps"
|
||||||
|
// _ "122.51.31.227/go-course/go18/devcloud/mcenter/apps"
|
||||||
|
// _ "122.51.31.227/go-course/go18/devcloud/mpaas/apps"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DevelopmentSetUp() {
|
||||||
|
// import 后自动执行的逻辑
|
||||||
|
// 工具对象的初始化, 需要的是绝对路径
|
||||||
|
ioc.DevelopmentSetupWithPath(os.Getenv("CONFIG_PATH"))
|
||||||
|
}
|
@ -12,7 +12,7 @@
|
|||||||
username = "root"
|
username = "root"
|
||||||
password = "123456"
|
password = "123456"
|
||||||
auto_migrate = true
|
auto_migrate = true
|
||||||
debug = true
|
debug = false
|
||||||
|
|
||||||
[mongo]
|
[mongo]
|
||||||
endpoints = ["127.0.0.1:27017"]
|
endpoints = ["127.0.0.1:27017"]
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
TX_API_KEY=
|
||||||
|
TX_API_SECRET=
|
@ -5,9 +5,13 @@ import (
|
|||||||
|
|
||||||
// mcenter 业务对象
|
// mcenter 业务对象
|
||||||
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps"
|
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps"
|
||||||
|
|
||||||
// audit 业务对象
|
// audit 业务对象
|
||||||
_ "122.51.31.227/go-course/go18/devcloud/audit/apps"
|
_ "122.51.31.227/go-course/go18/devcloud/audit/apps"
|
||||||
|
|
||||||
|
// mpaas 应用发布
|
||||||
|
_ "122.51.31.227/go-course/go18/devcloud/mpaas/apps"
|
||||||
|
|
||||||
// 非功能性模块
|
// 非功能性模块
|
||||||
_ "github.com/infraboard/mcube/v2/ioc/apps/apidoc/restful"
|
_ "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/health/restful"
|
||||||
|
9
devcloud/mcenter/apps/label/README..md
Normal file
9
devcloud/mcenter/apps/label/README..md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# 资源标签管理(数据字典)
|
||||||
|
|
||||||
|
+ key:value
|
||||||
|
|
||||||
|
Team Label: 用户组
|
||||||
|

|
||||||
|
|
||||||
|
## 如何基于关系性数据库设计Tree结构
|
||||||
|
|
37
devcloud/mcenter/apps/label/design.drawio
Normal file
37
devcloud/mcenter/apps/label/design.drawio
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<mxfile host="65bd71144e">
|
||||||
|
<diagram id="dN_L16zF_8OOMsYRBXKe" name="第 1 页">
|
||||||
|
<mxGraphModel dx="909" dy="295" 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="3" value="产品研发部(技术部)" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="20" y="80" width="800" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="4" value="产品/项目" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="30" y="180" width="120" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="6" value="开发" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="180" y="180" width="340" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="7" value="测试" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="540" y="180" width="120" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="8" value="运维" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="690" y="180" width="120" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="10" value="Web前端" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="195" y="260" width="70" height="50" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="11" value="后端" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="285" y="260" width="60" height="50" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="12" value="移动端" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="365" y="260" width="60" height="50" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="13" value="PC端" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="445" y="260" width="60" height="50" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
</mxfile>
|
15
devcloud/mcenter/apps/label/enum.go
Normal file
15
devcloud/mcenter/apps/label/enum.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package label
|
||||||
|
|
||||||
|
// 值类型
|
||||||
|
type VALUE_TYPE string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// 文本
|
||||||
|
VALUE_TYPE_TEXT VALUE_TYPE = "text"
|
||||||
|
// 布尔值, 只能是ture或者false
|
||||||
|
VALUE_TYPE_BOOLEAN VALUE_TYPE = "bool"
|
||||||
|
// 枚举
|
||||||
|
VALUE_TYPE_ENUM VALUE_TYPE = "enum"
|
||||||
|
// 基于url的远程选项拉去, 仅存储URL地址, 前端自己处理
|
||||||
|
VALUE_TYPE_HTTP_ENUM VALUE_TYPE = "http_enum"
|
||||||
|
)
|
31
devcloud/mcenter/apps/label/impl/impl.go
Normal file
31
devcloud/mcenter/apps/label/impl/impl.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package impl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/label"
|
||||||
|
"github.com/infraboard/mcube/v2/ioc"
|
||||||
|
"github.com/infraboard/mcube/v2/ioc/config/datasource"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
ioc.Controller().Registry(&LabelServiceImpl{})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ label.Service = (*LabelServiceImpl)(nil)
|
||||||
|
|
||||||
|
type LabelServiceImpl struct {
|
||||||
|
ioc.ObjectImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *LabelServiceImpl) Init() error {
|
||||||
|
if datasource.Get().AutoMigrate {
|
||||||
|
err := datasource.DB().AutoMigrate(&label.Label{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *LabelServiceImpl) Name() string {
|
||||||
|
return label.APP_NAME
|
||||||
|
}
|
18
devcloud/mcenter/apps/label/impl/impl_test.go
Normal file
18
devcloud/mcenter/apps/label/impl/impl_test.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package impl_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/label"
|
||||||
|
"122.51.31.227/go-course/go18/devcloud/mcenter/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
svc label.Service
|
||||||
|
ctx = context.Background()
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
test.DevelopmentSetUp()
|
||||||
|
svc = label.GetService()
|
||||||
|
}
|
74
devcloud/mcenter/apps/label/impl/label.go
Normal file
74
devcloud/mcenter/apps/label/impl/label.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package impl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/label"
|
||||||
|
"github.com/infraboard/mcube/v2/exception"
|
||||||
|
"github.com/infraboard/mcube/v2/ioc/config/datasource"
|
||||||
|
"github.com/infraboard/mcube/v2/types"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateLabel implements label.Service.
|
||||||
|
func (i *LabelServiceImpl) CreateLabel(ctx context.Context, in *label.CreateLabelRequest) (*label.Label, error) {
|
||||||
|
ins, err := label.NewLabel(in)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := datasource.DBFromCtx(ctx).
|
||||||
|
Create(ins).
|
||||||
|
Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ins, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryLabel implements label.Service.
|
||||||
|
func (i *LabelServiceImpl) QueryLabel(ctx context.Context, in *label.QueryLabelRequest) (*types.Set[*label.Label], error) {
|
||||||
|
set := types.New[*label.Label]()
|
||||||
|
|
||||||
|
query := datasource.DBFromCtx(ctx).Model(&label.Label{})
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// DescribeLabel implements label.Service.
|
||||||
|
func (i *LabelServiceImpl) DescribeLabel(ctx context.Context, in *label.DescribeLabelRequest) (*label.Label, error) {
|
||||||
|
query := datasource.DBFromCtx(ctx)
|
||||||
|
|
||||||
|
ins := &label.Label{}
|
||||||
|
if err := query.Where("id = ?", in.Id).First(ins).Error; err != nil {
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
return nil, exception.NewNotFound("label %s not found", in.Id)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ins, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteLabel implements label.Service.
|
||||||
|
func (i *LabelServiceImpl) DeleteLabel(ctx context.Context, in *label.DeleteLabelRequest) (*label.Label, error) {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateLabel implements label.Service.
|
||||||
|
func (i *LabelServiceImpl) UpdateLabel(ctx context.Context, in *label.UpdateLabelRequest) (*label.Label, error) {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
37
devcloud/mcenter/apps/label/impl/label_test.go
Normal file
37
devcloud/mcenter/apps/label/impl/label_test.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package impl_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/label"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateLabel(t *testing.T) {
|
||||||
|
req := label.NewCreateLabelRequest()
|
||||||
|
req.Key = "team"
|
||||||
|
req.KeyDesc = "小组"
|
||||||
|
req.ValueType = label.VALUE_TYPE_ENUM
|
||||||
|
req.AddEnumOption(&label.EnumOption{
|
||||||
|
Label: "开发一组",
|
||||||
|
Value: "dev01",
|
||||||
|
Children: []*label.EnumOption{
|
||||||
|
{Label: "后端开发", Value: "dev01.backend_developer"},
|
||||||
|
{Label: "前端开发", Value: "dev01.web_developer"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
ins, err := svc.CreateLabel(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(ins)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueryLabel(t *testing.T) {
|
||||||
|
req := label.NewQueryLabelRequest()
|
||||||
|
set, err := svc.QueryLabel(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(set)
|
||||||
|
}
|
57
devcloud/mcenter/apps/label/interface.go
Normal file
57
devcloud/mcenter/apps/label/interface.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package label
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/infraboard/mcube/v2/http/request"
|
||||||
|
"github.com/infraboard/mcube/v2/ioc"
|
||||||
|
"github.com/infraboard/mcube/v2/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
APP_NAME = "lable"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetService() Service {
|
||||||
|
return ioc.Controller().Get(APP_NAME).(Service)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Service interface {
|
||||||
|
// 创建标签
|
||||||
|
CreateLabel(context.Context, *CreateLabelRequest) (*Label, error)
|
||||||
|
// 修改标签
|
||||||
|
UpdateLabel(context.Context, *UpdateLabelRequest) (*Label, error)
|
||||||
|
// 删除标签
|
||||||
|
DeleteLabel(context.Context, *DeleteLabelRequest) (*Label, error)
|
||||||
|
// 查询标签列表
|
||||||
|
QueryLabel(context.Context, *QueryLabelRequest) (*types.Set[*Label], error)
|
||||||
|
// 查询标签列表
|
||||||
|
DescribeLabel(context.Context, *DescribeLabelRequest) (*Label, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateLabelRequest struct {
|
||||||
|
DescribeLabelRequest
|
||||||
|
// 更新人
|
||||||
|
UpdateBy string `json:"update_by"`
|
||||||
|
// 标签信息
|
||||||
|
Spec *CreateLabelRequest `json:"spec"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeleteLabelRequest struct {
|
||||||
|
DescribeLabelRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewQueryLabelRequest() *QueryLabelRequest {
|
||||||
|
return &QueryLabelRequest{
|
||||||
|
PageRequest: request.NewDefaultPageRequest(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type QueryLabelRequest struct {
|
||||||
|
*request.PageRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
type DescribeLabelRequest struct {
|
||||||
|
// 标签Id
|
||||||
|
Id string `json:"id"`
|
||||||
|
}
|
123
devcloud/mcenter/apps/label/model.go
Normal file
123
devcloud/mcenter/apps/label/model.go
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
package label
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/infraboard/mcube/v2/ioc/config/validator"
|
||||||
|
"github.com/infraboard/mcube/v2/tools/pretty"
|
||||||
|
"github.com/infraboard/modules/iam/apps"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewLabel(spc *CreateLabelRequest) (*Label, error) {
|
||||||
|
if err := spc.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Label{
|
||||||
|
ResourceMeta: *apps.NewResourceMeta(),
|
||||||
|
CreateLabelRequest: *spc,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Label struct {
|
||||||
|
// 基础数据
|
||||||
|
apps.ResourceMeta
|
||||||
|
// 空间定义
|
||||||
|
CreateLabelRequest `bson:",inline" gorm:"embedded"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Label) TableName() string {
|
||||||
|
return "labels"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Label) String() string {
|
||||||
|
return pretty.ToJSON(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCreateLabelRequest() *CreateLabelRequest {
|
||||||
|
return &CreateLabelRequest{
|
||||||
|
CreateCreateLabelSpec: CreateCreateLabelSpec{
|
||||||
|
Resources: []string{},
|
||||||
|
EnumOptions: []*EnumOption{},
|
||||||
|
Extras: map[string]string{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateLabelRequest struct {
|
||||||
|
// 创建人
|
||||||
|
CreateBy string `json:"create_by" bson:"create_by" gorm:"column:create_by;type:varchar(255)"`
|
||||||
|
// 标签的键
|
||||||
|
Domain string `json:"domain" bson:"domain" gorm:"column:domain;type:varchar(100)"`
|
||||||
|
// 标签的键
|
||||||
|
Namespace string `json:"namespace" bson:"namespace" gorm:"column:namespace;type:varchar(100)"`
|
||||||
|
// 用户参数
|
||||||
|
CreateCreateLabelSpec
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *CreateLabelRequest) Validate() error {
|
||||||
|
return validator.Validate(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *CreateLabelRequest) AddEnumOption(enums ...*EnumOption) *CreateLabelRequest {
|
||||||
|
r.EnumOptions = append(r.EnumOptions, enums...)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateCreateLabelSpec struct {
|
||||||
|
// 适用于那些资源
|
||||||
|
Resources []string `json:"resources" bson:"resources" gorm:"column:resources;type:json;serializer:json;" description:"适用于那些资源" optional:"true"`
|
||||||
|
|
||||||
|
// 标签的键, 标签的Key不允许修改
|
||||||
|
Key string `json:"key" bson:"key" gorm:"column:key;type:varchar(255)" validate:"required"`
|
||||||
|
// 标签的键的描述
|
||||||
|
KeyDesc string `json:"key_desc" bson:"key_desc" gorm:"column:key_desc;type:varchar(255)" validate:"required"`
|
||||||
|
// 标签的颜色
|
||||||
|
Color string `json:"color" bson:"color" gorm:"column:color;type:varchar(100)"`
|
||||||
|
|
||||||
|
// 标签的值相关信息, tech.dev.frontend01 tech.dev.backend01
|
||||||
|
// 值类型
|
||||||
|
ValueType VALUE_TYPE `json:"value_type" gorm:"column:value_type;type:varchar(20)" bson:"value_type"`
|
||||||
|
// 标签默认值
|
||||||
|
DefaultValue string `json:"default_value" gorm:"column:default_value;type:text" bson:"default_value"`
|
||||||
|
// 值描述
|
||||||
|
ValueDesc string `json:"value_desc" gorm:"column:value_desc;type:text" bson:"value_desc"`
|
||||||
|
// 是否是多选
|
||||||
|
Multiple bool `json:"multiple" bson:"multiple" gorm:"column:multiple;tinyint(1)"`
|
||||||
|
// 枚举值的选项
|
||||||
|
EnumOptions []*EnumOption `json:"enum_options,omitempty" bson:"enum_options" gorm:"column:enum_options;type:json;serializer:json;"`
|
||||||
|
// 基于Http枚举的配置
|
||||||
|
HttpEnumConfig HttpEnumConfig `json:"http_enum_config" gorm:"embedded" bson:"http_enum_config"`
|
||||||
|
// 值的样例
|
||||||
|
Example string `json:"example" bson:"example" gorm:"column:example;type:text"`
|
||||||
|
|
||||||
|
// 扩展属性
|
||||||
|
Extras map[string]string `json:"extras" bson:"extras" gorm:"column:extras;type:json;serializer:json;"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EnumOption struct {
|
||||||
|
// 选项的说明
|
||||||
|
Label string `json:"label" bson:"label"`
|
||||||
|
// 用户输入
|
||||||
|
Input string `json:"input" bson:"input" validate:"required"`
|
||||||
|
// 选项的值, 根据parent.input + children.input 自动生成
|
||||||
|
Value string `json:"value" bson:"value"`
|
||||||
|
// 是否禁止选中, 和前端UI组件配合使用
|
||||||
|
Disabled bool `json:"disabled" bson:"disabled"`
|
||||||
|
// 标签的颜色
|
||||||
|
Color string `json:"color" bson:"color"`
|
||||||
|
// 是否废弃
|
||||||
|
Deprecate bool `json:"deprecate" bson:"deprecate"`
|
||||||
|
// 废弃说明
|
||||||
|
DeprecateDesc string `json:"deprecate_desc" bson:"deprecate_desc"`
|
||||||
|
// 枚举的子选项
|
||||||
|
Children []*EnumOption `json:"children,omitempty" bson:"children"`
|
||||||
|
// 扩展属性
|
||||||
|
Extensions map[string]string `json:"extensions" bson:"extensions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HttpEnumConfig struct {
|
||||||
|
// 基于枚举的URL, 注意只支持Get方法
|
||||||
|
Url string `json:"url" bson:"url" gorm:"column:http_enum_config_url;type:text"`
|
||||||
|
// Enum Label映射的字段名
|
||||||
|
KeyFiled string `json:"enum_label_name" bson:"enum_label_name" gorm:"column:http_enum_config_key_filed;type:varchar(100)"`
|
||||||
|
// Enum Value映射的字段名
|
||||||
|
ValueFiled string `json:"enum_label_value" bson:"enum_label_value" gorm:"column:http_enum_config_value_filed;type:varchar(100)"`
|
||||||
|
}
|
@ -2,8 +2,8 @@ package impl
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/endpoint"
|
|
||||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/namespace"
|
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/namespace"
|
||||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/policy"
|
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/policy"
|
||||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/role"
|
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/role"
|
||||||
@ -36,8 +36,8 @@ func (i *PolicyServiceImpl) QueryNamespace(ctx context.Context, in *policy.Query
|
|||||||
|
|
||||||
// 查询用户可以访问的Api接口
|
// 查询用户可以访问的Api接口
|
||||||
// 找到用户可以访问的角色列表,然后在找出角色对应的Api访问权限
|
// 找到用户可以访问的角色列表,然后在找出角色对应的Api访问权限
|
||||||
func (i *PolicyServiceImpl) QueryEndpoint(ctx context.Context, in *policy.QueryEndpointRequest) (*types.Set[*endpoint.Endpoint], error) {
|
func (i *PolicyServiceImpl) QueryEndpoint(ctx context.Context, in *policy.QueryEndpointRequest) (*policy.QueryEndpointResponse, error) {
|
||||||
set := types.New[*endpoint.Endpoint]()
|
resp := policy.NewQueryEndpointResponse()
|
||||||
policies, err := i.QueryPolicy(ctx,
|
policies, err := i.QueryPolicy(ctx,
|
||||||
policy.NewQueryPolicyRequest().
|
policy.NewQueryPolicyRequest().
|
||||||
SetSkipPage(true).
|
SetSkipPage(true).
|
||||||
@ -49,19 +49,30 @@ func (i *PolicyServiceImpl) QueryEndpoint(ctx context.Context, in *policy.QueryE
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
roleReq := role.NewQueryMatchedEndpointRequest()
|
// 没有权限
|
||||||
policies.ForEach(func(t *policy.Policy) {
|
if policies.Len() == 0 {
|
||||||
roleReq.Add(t.RoleId)
|
return resp, nil
|
||||||
})
|
}
|
||||||
|
|
||||||
|
if policies.Len() > 1 {
|
||||||
|
return nil, fmt.Errorf("同一个空间下, 一个用户有多条[%d]授权", policies.Len())
|
||||||
|
}
|
||||||
|
|
||||||
|
p := policies.First()
|
||||||
|
roleReq := role.NewQueryMatchedEndpointRequest()
|
||||||
|
roleReq.Add(p.RoleId...)
|
||||||
|
|
||||||
|
// p.ResourceScope
|
||||||
|
resp.ResourceScope = p.ResourceScope
|
||||||
if policies.Len() > 0 {
|
if policies.Len() > 0 {
|
||||||
set, err = role.GetService().QueryMatchedEndpoint(ctx, roleReq)
|
set, err := role.GetService().QueryMatchedEndpoint(ctx, roleReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
resp.Items = set.Items
|
||||||
}
|
}
|
||||||
|
|
||||||
return set, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 校验Api接口权限
|
// 校验Api接口权限
|
||||||
@ -69,7 +80,7 @@ func (i *PolicyServiceImpl) ValidateEndpointPermission(ctx context.Context, in *
|
|||||||
resp := policy.NewValidateEndpointPermissionResponse(*in)
|
resp := policy.NewValidateEndpointPermissionResponse(*in)
|
||||||
|
|
||||||
// 空间Owner有所有权限
|
// 空间Owner有所有权限
|
||||||
ns, err := namespace.GetService().DescribeNamespace(ctx, namespace.NewDescribeNamespaceRequest().SetNamespaceId(in.NamespaceId))
|
ns, err := namespace.GetService().DescribeNamespace(ctx, namespace.NewDescribeNamespaceRequest().SetNamespaceId(in.GetNamespaceId()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -81,15 +92,20 @@ func (i *PolicyServiceImpl) ValidateEndpointPermission(ctx context.Context, in *
|
|||||||
// 非空间管理员需要独立鉴权, 查询用户可以访问的API列表
|
// 非空间管理员需要独立鉴权, 查询用户可以访问的API列表
|
||||||
endpointReq := policy.NewQueryEndpointRequest()
|
endpointReq := policy.NewQueryEndpointRequest()
|
||||||
endpointReq.UserId = in.UserId
|
endpointReq.UserId = in.UserId
|
||||||
endpointReq.NamespaceId = in.NamespaceId
|
endpointReq.NamespaceId = in.GetNamespaceId()
|
||||||
endpointSet, err := i.QueryEndpoint(ctx, endpointReq)
|
endpointSet, err := i.QueryEndpoint(ctx, endpointReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 补充Api访问的scope
|
||||||
|
resp.ResourceScope = endpointSet.ResourceScope
|
||||||
|
|
||||||
for _, item := range endpointSet.Items {
|
for _, item := range endpointSet.Items {
|
||||||
if item.IsMatched(in.Service, in.Method, in.Path) {
|
if item.IsMatched(in.Service, in.Method, in.Path) {
|
||||||
resp.HasPermission = true
|
resp.HasPermission = true
|
||||||
resp.Endpoint = item
|
resp.Endpoint = item
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
func TestQueryNamespace(t *testing.T) {
|
func TestQueryNamespace(t *testing.T) {
|
||||||
req := policy.NewQueryNamespaceRequest()
|
req := policy.NewQueryNamespaceRequest()
|
||||||
req.UserId = 1
|
req.UserId = 2
|
||||||
set, err := impl.QueryNamespace(ctx, req)
|
set, err := impl.QueryNamespace(ctx, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -29,8 +29,8 @@ func TestQueryEndpoint(t *testing.T) {
|
|||||||
|
|
||||||
func TestValidateEndpointPermission(t *testing.T) {
|
func TestValidateEndpointPermission(t *testing.T) {
|
||||||
req := policy.NewValidateEndpointPermissionRequest()
|
req := policy.NewValidateEndpointPermissionRequest()
|
||||||
req.UserId = 1
|
req.UserId = 3
|
||||||
req.NamespaceId = 1
|
req.SetNamespaceId(1)
|
||||||
req.Service = "devcloud"
|
req.Service = "devcloud"
|
||||||
req.Method = "GET"
|
req.Method = "GET"
|
||||||
req.Path = "/api/devcloud/v1/users/"
|
req.Path = "/api/devcloud/v1/users/"
|
||||||
|
@ -2,6 +2,7 @@ package impl
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/namespace"
|
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/namespace"
|
||||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/policy"
|
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/policy"
|
||||||
@ -79,7 +80,7 @@ func (i *PolicyServiceImpl) QueryPolicy(ctx context.Context, in *policy.QueryPol
|
|||||||
if in.WithRole {
|
if in.WithRole {
|
||||||
roleReq := role.NewQueryRoleRequest()
|
roleReq := role.NewQueryRoleRequest()
|
||||||
set.ForEach(func(t *policy.Policy) {
|
set.ForEach(func(t *policy.Policy) {
|
||||||
roleReq.AddRoleId(t.RoleId)
|
roleReq.AddRoleId(t.RoleId...)
|
||||||
})
|
})
|
||||||
roleSet, err := role.GetService().QueryRole(ctx, roleReq)
|
roleSet, err := role.GetService().QueryRole(ctx, roleReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -87,7 +88,7 @@ func (i *PolicyServiceImpl) QueryPolicy(ctx context.Context, in *policy.QueryPol
|
|||||||
}
|
}
|
||||||
set.ForEach(func(p *policy.Policy) {
|
set.ForEach(func(p *policy.Policy) {
|
||||||
p.Role = roleSet.Filter(func(t *role.Role) bool {
|
p.Role = roleSet.Filter(func(t *role.Role) bool {
|
||||||
return p.RoleId == t.Id
|
return slices.Contains(p.RoleId, t.Id)
|
||||||
}).First()
|
}).First()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -20,9 +20,14 @@ func TestQueryPolicy(t *testing.T) {
|
|||||||
|
|
||||||
func TestCreatePolicy(t *testing.T) {
|
func TestCreatePolicy(t *testing.T) {
|
||||||
req := policy.NewCreatePolicyRequest()
|
req := policy.NewCreatePolicyRequest()
|
||||||
req.SetNamespaceId(1)
|
// guest
|
||||||
req.UserId = 2
|
req.UserId = 2
|
||||||
req.RoleId = 1
|
// 开发
|
||||||
|
req.RoleId = []uint64{3}
|
||||||
|
// default
|
||||||
|
req.SetNamespaceId(1)
|
||||||
|
// 开发小组1的资源
|
||||||
|
req.SetScope("team", []string{"dev01"})
|
||||||
set, err := impl.CreatePolicy(ctx, req)
|
set, err := impl.CreatePolicy(ctx, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -132,17 +132,28 @@ type PermissionService interface {
|
|||||||
// 查询用户可以访问的菜单
|
// 查询用户可以访问的菜单
|
||||||
QueryMenu(context.Context, *QueryMenuRequest) (*types.Set[*view.Menu], error)
|
QueryMenu(context.Context, *QueryMenuRequest) (*types.Set[*view.Menu], error)
|
||||||
// 查询用户可以访问的Api接口
|
// 查询用户可以访问的Api接口
|
||||||
QueryEndpoint(context.Context, *QueryEndpointRequest) (*types.Set[*endpoint.Endpoint], error)
|
QueryEndpoint(context.Context, *QueryEndpointRequest) (*QueryEndpointResponse, error)
|
||||||
// 校验页面权限
|
// 校验页面权限
|
||||||
ValidatePagePermission(context.Context, *ValidatePagePermissionRequest) (*ValidatePagePermissionResponse, error)
|
ValidatePagePermission(context.Context, *ValidatePagePermissionRequest) (*ValidatePagePermissionResponse, error)
|
||||||
// 校验接口权限
|
// 校验接口权限
|
||||||
ValidateEndpointPermission(context.Context, *ValidateEndpointPermissionRequest) (*ValidateEndpointPermissionResponse, error)
|
ValidateEndpointPermission(context.Context, *ValidateEndpointPermissionRequest) (*ValidateEndpointPermissionResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewQueryEndpointResponse() *QueryEndpointResponse {
|
||||||
|
return &QueryEndpointResponse{
|
||||||
|
Items: []*endpoint.Endpoint{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type QueryEndpointResponse struct {
|
||||||
|
ResourceScope
|
||||||
|
Items []*endpoint.Endpoint
|
||||||
|
}
|
||||||
|
|
||||||
type ValidatePagePermissionRequest struct {
|
type ValidatePagePermissionRequest struct {
|
||||||
UserId uint64 `json:"user_id" form:"user_id"`
|
UserId uint64 `json:"user_id" form:"user_id"`
|
||||||
NamespaceId uint64 `json:"namespace_id" form:"namespace_id"`
|
|
||||||
Path string `json:"path" form:"path"`
|
Path string `json:"path" form:"path"`
|
||||||
|
ResourceScope
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewValidatePagePermissionResponse(req ValidatePagePermissionRequest) *ValidatePagePermissionResponse {
|
func NewValidatePagePermissionResponse(req ValidatePagePermissionRequest) *ValidatePagePermissionResponse {
|
||||||
@ -163,10 +174,10 @@ func NewValidateEndpointPermissionRequest() *ValidateEndpointPermissionRequest {
|
|||||||
|
|
||||||
type ValidateEndpointPermissionRequest struct {
|
type ValidateEndpointPermissionRequest struct {
|
||||||
UserId uint64 `json:"user_id" form:"user_id"`
|
UserId uint64 `json:"user_id" form:"user_id"`
|
||||||
NamespaceId uint64 `json:"namespace_id" form:"namespace_id"`
|
|
||||||
Service string `json:"service" form:"service"`
|
Service string `json:"service" form:"service"`
|
||||||
Path string `json:"path" form:"path"`
|
Path string `json:"path" form:"path"`
|
||||||
Method string `json:"method" form:"method"`
|
Method string `json:"method" form:"method"`
|
||||||
|
ResourceScope
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewValidateEndpointPermissionResponse(req ValidateEndpointPermissionRequest) *ValidateEndpointPermissionResponse {
|
func NewValidateEndpointPermissionResponse(req ValidateEndpointPermissionRequest) *ValidateEndpointPermissionResponse {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package policy
|
package policy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/namespace"
|
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/namespace"
|
||||||
@ -9,6 +10,9 @@ import (
|
|||||||
"github.com/infraboard/mcube/v2/ioc/config/validator"
|
"github.com/infraboard/mcube/v2/ioc/config/validator"
|
||||||
"github.com/infraboard/mcube/v2/tools/pretty"
|
"github.com/infraboard/mcube/v2/tools/pretty"
|
||||||
"github.com/infraboard/modules/iam/apps"
|
"github.com/infraboard/modules/iam/apps"
|
||||||
|
"gorm.io/datatypes"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewPolicy() *Policy {
|
func NewPolicy() *Policy {
|
||||||
@ -40,24 +44,24 @@ func (p *Policy) String() string {
|
|||||||
|
|
||||||
func NewCreatePolicyRequest() *CreatePolicyRequest {
|
func NewCreatePolicyRequest() *CreatePolicyRequest {
|
||||||
return &CreatePolicyRequest{
|
return &CreatePolicyRequest{
|
||||||
|
ResourceScope: ResourceScope{
|
||||||
|
Scope: map[string][]string{},
|
||||||
|
},
|
||||||
|
RoleId: []uint64{},
|
||||||
Extras: map[string]string{},
|
Extras: map[string]string{},
|
||||||
Scope: map[string]string{},
|
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
ReadOnly: false,
|
ReadOnly: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreatePolicyRequest struct {
|
type CreatePolicyRequest struct {
|
||||||
|
ResourceScope
|
||||||
// 创建者
|
// 创建者
|
||||||
CreateBy uint64 `json:"create_by" bson:"create_by" gorm:"column:create_by;type:uint" description:"创建者" optional:"true"`
|
CreateBy uint64 `json:"create_by" bson:"create_by" gorm:"column:create_by;type:uint" description:"创建者" optional:"true"`
|
||||||
// 空间
|
|
||||||
NamespaceId *uint64 `json:"namespace_id" bson:"namespace_id" gorm:"column:namespace_id;type:varchar(200);index" description:"策略生效的空间Id" optional:"true"`
|
|
||||||
// 用户Id
|
// 用户Id
|
||||||
UserId uint64 `json:"user_id" bson:"user_id" gorm:"column:user_id;type:uint;not null;index" validate:"required" description:"被授权的用户"`
|
UserId uint64 `json:"user_id" bson:"user_id" gorm:"column:user_id;type:uint;not null;index" validate:"required" description:"被授权的用户"`
|
||||||
// 角色Id
|
// 角色Id
|
||||||
RoleId uint64 `json:"role_id" bson:"role_id" gorm:"column:role_id;type:uint;not null;index" validate:"required" description:"被关联的角色"`
|
RoleId []uint64 `json:"role_id" bson:"role_id" gorm:"column:role_id;type:json;not null;serializer:json" validate:"required" description:"被关联的角色"`
|
||||||
// 访问范围, 需要提前定义scope, 比如环境
|
|
||||||
Scope map[string]string `json:"scope" bson:"scope" gorm:"column:scope;serializer:json;type:json" description:"数据访问的范围" optional:"true"`
|
|
||||||
// 策略过期时间
|
// 策略过期时间
|
||||||
ExpiredTime *time.Time `json:"expired_time" bson:"expired_time" gorm:"column:expired_time;type:timestamp;index" description:"策略过期时间" optional:"true"`
|
ExpiredTime *time.Time `json:"expired_time" bson:"expired_time" gorm:"column:expired_time;type:timestamp;index" description:"策略过期时间" optional:"true"`
|
||||||
// 只读策略, 不允许用户修改, 一般用于系统管理
|
// 只读策略, 不允许用户修改, 一般用于系统管理
|
||||||
@ -70,6 +74,85 @@ type CreatePolicyRequest struct {
|
|||||||
Extras map[string]string `json:"extras" bson:"extras" gorm:"column:extras;serializer:json;type:json" description:"扩展信息" optional:"true"`
|
Extras map[string]string `json:"extras" bson:"extras" gorm:"column:extras;serializer:json;type:json" description:"扩展信息" optional:"true"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 资源需要组合ResourceLabel使用
|
||||||
|
type ResourceScope struct {
|
||||||
|
// 空间
|
||||||
|
NamespaceId *uint64 `json:"namespace_id" bson:"namespace_id" gorm:"column:namespace_id;type:varchar(200);index" description:"策略生效的空间Id" optional:"true"`
|
||||||
|
// 访问范围, 需要提前定义scope, 比如环境, 后端开发小组,开发资源
|
||||||
|
Scope map[string][]string `json:"scope" bson:"scope" gorm:"column:scope;serializer:json;type:json" description:"数据访问的范围" optional:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r ResourceScope) String() string {
|
||||||
|
return pretty.ToJSON(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ResourceScope) BuildMySQLPrefixBlob() {
|
||||||
|
for k := range r.Scope {
|
||||||
|
for i := range r.Scope[k] {
|
||||||
|
if !strings.HasSuffix(r.Scope[k][i], "%") {
|
||||||
|
r.Scope[k][i] += "%"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ResourceScope) SetNamespaceId(v uint64) {
|
||||||
|
r.NamespaceId = &v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ResourceScope) GetNamespaceId() uint64 {
|
||||||
|
if r.NamespaceId == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return *r.NamespaceId
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *ResourceScope) SetScope(key string, value []string) {
|
||||||
|
if l.Scope == nil {
|
||||||
|
l.Scope = map[string][]string{}
|
||||||
|
}
|
||||||
|
l.Scope[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r ResourceScope) GormResourceFilter(query *gorm.DB) *gorm.DB {
|
||||||
|
if r.NamespaceId != nil {
|
||||||
|
query = query.Where("namespace_id = ?", r.NamespaceId)
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, values := range r.Scope {
|
||||||
|
if len(values) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建"标签不存在"条件
|
||||||
|
notHasKey := clause.Not(datatypes.JSONQuery("label").HasKey(key))
|
||||||
|
|
||||||
|
// 构建"标签值匹配"条件
|
||||||
|
var valueMatches []clause.Expression
|
||||||
|
for _, val := range values {
|
||||||
|
if strings.Contains(val, "%") {
|
||||||
|
// 如果值包含通配符%,使用LIKE条件
|
||||||
|
valueMatches = append(valueMatches,
|
||||||
|
clause.Expr{
|
||||||
|
SQL: "JSON_UNQUOTE(JSON_EXTRACT(label, ?)) LIKE ?",
|
||||||
|
Vars: []any{"$." + key, val},
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// 否则使用精确匹配
|
||||||
|
valueMatches = append(valueMatches,
|
||||||
|
datatypes.JSONQuery("label").Equals(val, key))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组合条件:标签不存在 OR 标签值匹配
|
||||||
|
query = query.Where(clause.Or(
|
||||||
|
notHasKey,
|
||||||
|
clause.Or(valueMatches...),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
func (r *CreatePolicyRequest) Validate() error {
|
func (r *CreatePolicyRequest) Validate() error {
|
||||||
return validator.Validate(r)
|
return validator.Validate(r)
|
||||||
}
|
}
|
||||||
@ -78,3 +161,21 @@ func (r *CreatePolicyRequest) SetNamespaceId(namespaceId uint64) *CreatePolicyRe
|
|||||||
r.NamespaceId = &namespaceId
|
r.NamespaceId = &namespaceId
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ResourceLabel struct {
|
||||||
|
// 空间
|
||||||
|
NamespaceId *uint64 `json:"namespace_id" bson:"namespace_id" gorm:"column:namespace_id;type:varchar(200);index" description:"策略生效的空间Id" optional:"true"`
|
||||||
|
// 访问范围, 需要提前定义scope, 比如环境, 后端开发小组,开发资源
|
||||||
|
Label map[string]string `json:"label" bson:"label" gorm:"column:label;serializer:json;type:json" description:"数据访问的范围" optional:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *ResourceLabel) SetNamespaceId(v uint64) {
|
||||||
|
l.NamespaceId = &v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *ResourceLabel) SetLabel(key, value string) {
|
||||||
|
if l.Label == nil {
|
||||||
|
l.Label = map[string]string{}
|
||||||
|
}
|
||||||
|
l.Label[key] = value
|
||||||
|
}
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
// 鉴权
|
// 鉴权
|
||||||
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps/endpoint/impl"
|
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps/endpoint/impl"
|
||||||
|
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps/label/impl"
|
||||||
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps/namespace/impl"
|
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps/namespace/impl"
|
||||||
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps/policy/impl"
|
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps/policy/impl"
|
||||||
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps/role/impl"
|
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps/role/impl"
|
||||||
|
@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
func TestQueryApiPermission(t *testing.T) {
|
func TestQueryApiPermission(t *testing.T) {
|
||||||
req := role.NewQueryApiPermissionRequest()
|
req := role.NewQueryApiPermissionRequest()
|
||||||
req.AddRoleId(2)
|
req.AddRoleId(3)
|
||||||
set, err := impl.QueryApiPermission(ctx, req)
|
set, err := impl.QueryApiPermission(ctx, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -17,8 +17,11 @@ func TestQueryApiPermission(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAddApiPermission(t *testing.T) {
|
func TestAddApiPermission(t *testing.T) {
|
||||||
req := role.NewAddApiPermissionRequest(1)
|
req := role.NewAddApiPermissionRequest(3)
|
||||||
req.Add(role.NewResourceActionApiPermissionSpec("devcloud", "user", "list"))
|
req.Add(
|
||||||
|
role.NewResourceActionApiPermissionSpec("devcloud", "user", "list"),
|
||||||
|
// role.NewResourceActionApiPermissionSpec("devcloud", "application", "list"),
|
||||||
|
)
|
||||||
set, err := impl.AddApiPermission(ctx, req)
|
set, err := impl.AddApiPermission(ctx, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -28,7 +31,7 @@ func TestAddApiPermission(t *testing.T) {
|
|||||||
|
|
||||||
func TestQueryMatchedEndpoint(t *testing.T) {
|
func TestQueryMatchedEndpoint(t *testing.T) {
|
||||||
req := role.NewQueryMatchedEndpointRequest()
|
req := role.NewQueryMatchedEndpointRequest()
|
||||||
req.Add(1)
|
req.Add(3)
|
||||||
set, err := impl.QueryMatchedEndpoint(ctx, req)
|
set, err := impl.QueryMatchedEndpoint(ctx, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -49,8 +49,8 @@ func (i *TokenServiceImpl) IssueToken(ctx context.Context, in *token.IssueTokenR
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if tk.NamespaceId == 0 {
|
if tk.NamespaceId == nil {
|
||||||
tk.NamespaceId = 1
|
tk.SetNamespaceId(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保持Token
|
// 保持Token
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/policy"
|
||||||
"github.com/infraboard/mcube/v2/exception"
|
"github.com/infraboard/mcube/v2/exception"
|
||||||
"github.com/infraboard/mcube/v2/tools/pretty"
|
"github.com/infraboard/mcube/v2/tools/pretty"
|
||||||
)
|
)
|
||||||
@ -56,7 +57,6 @@ func NewToken() *Token {
|
|||||||
IssueAt: time.Now(),
|
IssueAt: time.Now(),
|
||||||
Status: NewStatus(),
|
Status: NewStatus(),
|
||||||
Extras: map[string]string{},
|
Extras: map[string]string{},
|
||||||
Scope: map[string]string{},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return tk
|
return tk
|
||||||
@ -77,12 +77,14 @@ type Token struct {
|
|||||||
UserName string `json:"user_name" gorm:"column:user_name;type:varchar(100);not null;index" description:"持有该Token的用户名称"`
|
UserName string `json:"user_name" gorm:"column:user_name;type:varchar(100);not null;index" description:"持有该Token的用户名称"`
|
||||||
// 是不是管理员
|
// 是不是管理员
|
||||||
IsAdmin bool `json:"is_admin" gorm:"column:is_admin;type:tinyint(1)" description:"是不是管理员"`
|
IsAdmin bool `json:"is_admin" gorm:"column:is_admin;type:tinyint(1)" description:"是不是管理员"`
|
||||||
// 令牌生效空间Id
|
|
||||||
NamespaceId uint64 `json:"namespace_id" gorm:"column:namespace_id;type:uint;index" description:"令牌所属空间Id"`
|
// 令牌生效空间Ids
|
||||||
|
policy.ResourceScope
|
||||||
|
// NamespaceId uint64 `json:"namespace_id" gorm:"column:namespace_id;type:uint;index" description:"令牌所属空间Id"`
|
||||||
// 令牌生效空间名称
|
// 令牌生效空间名称
|
||||||
NamespaceName string `json:"namespace_name" gorm:"column:namespace_name;type:varchar(100);index" description:"令牌所属空间"`
|
NamespaceName string `json:"namespace_name" gorm:"column:namespace_name;type:varchar(100);index" description:"令牌所属空间"`
|
||||||
// 访问范围定义, 鉴权完成后补充
|
// // 访问范围定义, 鉴权完成后补充
|
||||||
Scope map[string]string `json:"scope" gorm:"column:scope;type:varchar(100);serializer:json" description:"令牌访问范围定义"`
|
// Scope map[string][]string `json:"scope" gorm:"column:scope;type:varchar(100);serializer:json" description:"令牌访问范围定义"`
|
||||||
// 颁发给用户的访问令牌(用户需要携带Token来访问接口)
|
// 颁发给用户的访问令牌(用户需要携带Token来访问接口)
|
||||||
AccessToken string `json:"access_token" gorm:"column:access_token;type:varchar(100);not null;uniqueIndex" description:"访问令牌"`
|
AccessToken string `json:"access_token" gorm:"column:access_token;type:varchar(100);not null;uniqueIndex" description:"访问令牌"`
|
||||||
// 访问令牌过期时间
|
// 访问令牌过期时间
|
||||||
@ -105,6 +107,10 @@ func (t *Token) TableName() string {
|
|||||||
return "tokens"
|
return "tokens"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Token) String() string {
|
||||||
|
return pretty.ToJSON(r)
|
||||||
|
}
|
||||||
|
|
||||||
// 判断访问令牌是否过期,没设置代表用不过期
|
// 判断访问令牌是否过期,没设置代表用不过期
|
||||||
func (t *Token) IsAccessTokenExpired() error {
|
func (t *Token) IsAccessTokenExpired() error {
|
||||||
if t.AccessTokenExpiredAt != nil {
|
if t.AccessTokenExpiredAt != nil {
|
||||||
@ -158,10 +164,6 @@ func (t *Token) SetRefreshTokenExpiredAt(v time.Time) {
|
|||||||
t.RefreshTokenExpiredAt = &v
|
t.RefreshTokenExpiredAt = &v
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Token) String() string {
|
|
||||||
return pretty.ToJSON(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Token) SetIssuer(issuer string) *Token {
|
func (t *Token) SetIssuer(issuer string) *Token {
|
||||||
t.Issuer = issuer
|
t.Issuer = issuer
|
||||||
return t
|
return t
|
||||||
|
@ -92,6 +92,14 @@ func (c *Checker) Check(r *restful.Request, w *restful.Response, next *restful.F
|
|||||||
response.Failed(w, err)
|
response.Failed(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果校验成功,需要把 用户的身份信息,放到请求的上下文中,方便后面的逻辑获取
|
||||||
|
// 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 请求处理
|
// 请求处理
|
||||||
@ -111,13 +119,6 @@ func (c *Checker) CheckToken(r *restful.Request) (*token.Token, error) {
|
|||||||
return nil, err
|
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
|
return tk, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,7 +132,7 @@ func (c *Checker) CheckPolicy(r *restful.Request, tk *token.Token, route *endpoi
|
|||||||
if route.HasRequiredRole() {
|
if route.HasRequiredRole() {
|
||||||
set, err := c.policy.QueryPolicy(r.Request.Context(),
|
set, err := c.policy.QueryPolicy(r.Request.Context(),
|
||||||
policy.NewQueryPolicyRequest().
|
policy.NewQueryPolicyRequest().
|
||||||
SetNamespaceId(tk.NamespaceId).
|
SetNamespaceId(tk.GetNamespaceId()).
|
||||||
SetUserId(tk.UserId).
|
SetUserId(tk.UserId).
|
||||||
SetExpired(false).
|
SetExpired(false).
|
||||||
SetEnabled(true).
|
SetEnabled(true).
|
||||||
@ -145,6 +146,9 @@ func (c *Checker) CheckPolicy(r *restful.Request, tk *token.Token, route *endpoi
|
|||||||
p := set.Items[i]
|
p := set.Items[i]
|
||||||
if route.IsRequireRole(p.Role.Name) {
|
if route.IsRequireRole(p.Role.Name) {
|
||||||
hasPerm = true
|
hasPerm = true
|
||||||
|
|
||||||
|
// 把这个scope传递下去
|
||||||
|
tk.Scope = p.Scope
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -157,7 +161,7 @@ func (c *Checker) CheckPolicy(r *restful.Request, tk *token.Token, route *endpoi
|
|||||||
if route.RequiredPerm {
|
if route.RequiredPerm {
|
||||||
validateReq := policy.NewValidateEndpointPermissionRequest()
|
validateReq := policy.NewValidateEndpointPermissionRequest()
|
||||||
validateReq.UserId = tk.UserId
|
validateReq.UserId = tk.UserId
|
||||||
validateReq.NamespaceId = tk.NamespaceId
|
validateReq.ResourceScope = tk.ResourceScope
|
||||||
validateReq.Service = application.Get().GetAppName()
|
validateReq.Service = application.Get().GetAppName()
|
||||||
validateReq.Method = route.Method
|
validateReq.Method = route.Method
|
||||||
validateReq.Path = route.Path
|
validateReq.Path = route.Path
|
||||||
@ -168,6 +172,9 @@ func (c *Checker) CheckPolicy(r *restful.Request, tk *token.Token, route *endpoi
|
|||||||
if !resp.HasPermission {
|
if !resp.HasPermission {
|
||||||
return exception.NewPermissionDeny("无权限访问")
|
return exception.NewPermissionDeny("无权限访问")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tk.ResourceScope = resp.ResourceScope
|
||||||
|
tk.BuildMySQLPrefixBlob()
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
# 应用管理
|
# 应用管理
|
||||||
|
|
||||||
应用的核心是代码, 围绕这应用的核心代码, SCM的仓库地址管理
|
应用的核心是代码, 围绕这应用的核心代码, SCM的仓库地址管理
|
||||||
|
|
||||||
|
|
||||||
|
## 应用CRUD
|
||||||
|
|
||||||
|
最麻烦的基于标签的动态匹配(核心权限)
|
||||||
|
|
||||||
|
58
devcloud/mpaas/apps/application/api/api.go
Normal file
58
devcloud/mpaas/apps/application/api/api.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"122.51.31.227/go-course/go18/devcloud/audit/audit"
|
||||||
|
"122.51.31.227/go-course/go18/devcloud/mcenter/permission"
|
||||||
|
"122.51.31.227/go-course/go18/devcloud/mpaas/apps/application"
|
||||||
|
"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 application.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *UserRestfulApiHandler) Name() string {
|
||||||
|
return "applications"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *UserRestfulApiHandler) Init() error {
|
||||||
|
h.svc = application.GetService()
|
||||||
|
|
||||||
|
tags := []string{"应用管理"}
|
||||||
|
ws := gorestful.ObjectRouter(h)
|
||||||
|
// required_auth=true/false
|
||||||
|
ws.Route(ws.GET("").To(h.QueryApplication).
|
||||||
|
Doc("应用列表查询").
|
||||||
|
Metadata(restfulspec.KeyOpenAPITags, tags).
|
||||||
|
// 这个开关怎么生效
|
||||||
|
// 中间件需求读取接口的描述信息,来决定是否需要认证
|
||||||
|
Metadata(permission.Auth(true)).
|
||||||
|
Metadata(permission.Permission(true)).
|
||||||
|
Metadata(permission.Resource("application")).
|
||||||
|
Metadata(permission.Action("list")).
|
||||||
|
Metadata(audit.Enable(true)).
|
||||||
|
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 []application.Application `json:"items"`
|
||||||
|
}
|
33
devcloud/mpaas/apps/application/api/application.go
Normal file
33
devcloud/mpaas/apps/application/api/application.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/token"
|
||||||
|
"122.51.31.227/go-course/go18/devcloud/mpaas/apps/application"
|
||||||
|
"github.com/emicklei/go-restful/v3"
|
||||||
|
"github.com/gin-gonic/gin/binding"
|
||||||
|
"github.com/infraboard/mcube/v2/http/restful/response"
|
||||||
|
"github.com/infraboard/mcube/v2/ioc/config/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h *UserRestfulApiHandler) QueryApplication(r *restful.Request, w *restful.Response) {
|
||||||
|
req := application.NewQueryApplicationRequest()
|
||||||
|
|
||||||
|
// 过滤条件在认证完成后的上下文中
|
||||||
|
tk := token.GetTokenFromCtx(r.Request.Context())
|
||||||
|
req.ResourceScope = tk.ResourceScope
|
||||||
|
log.L().Debug().Msgf("resource scope: %s", tk.ResourceScope)
|
||||||
|
|
||||||
|
// 用户的参数
|
||||||
|
if err := binding.Query.Bind(r.Request, &req.QueryApplicationRequestSpec); err != nil {
|
||||||
|
response.Failed(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
set, err := h.svc.QueryApplication(r.Request.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
response.Failed(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Success(w, set)
|
||||||
|
}
|
19
devcloud/mpaas/apps/application/fn_test.go
Normal file
19
devcloud/mpaas/apps/application/fn_test.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package application_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"122.51.31.227/go-course/go18/devcloud/mpaas/apps/application"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestXxx(t *testing.T) {
|
||||||
|
app := &application.Application{}
|
||||||
|
tt := reflect.TypeOf(app)
|
||||||
|
// If app is a pointer, get the element type
|
||||||
|
if tt.Kind() == reflect.Ptr {
|
||||||
|
tt = tt.Elem()
|
||||||
|
}
|
||||||
|
fnName := tt.PkgPath() + "." + tt.Name()
|
||||||
|
t.Log(fnName)
|
||||||
|
}
|
98
devcloud/mpaas/apps/application/impl/application.go
Normal file
98
devcloud/mpaas/apps/application/impl/application.go
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
package impl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"122.51.31.227/go-course/go18/devcloud/mpaas/apps/application"
|
||||||
|
"github.com/infraboard/mcube/v2/exception"
|
||||||
|
"github.com/infraboard/mcube/v2/ioc/config/datasource"
|
||||||
|
"github.com/infraboard/mcube/v2/types"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateApplication implements application.Service.
|
||||||
|
func (i *ApplicationServiceImpl) CreateApplication(ctx context.Context, in *application.CreateApplicationRequest) (*application.Application, error) {
|
||||||
|
ins, err := application.NewApplication(*in)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := datasource.DBFromCtx(ctx).
|
||||||
|
Save(ins).
|
||||||
|
Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ins, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryApplication implements application.Service.
|
||||||
|
func (i *ApplicationServiceImpl) QueryApplication(ctx context.Context, in *application.QueryApplicationRequest) (*types.Set[*application.Application], error) {
|
||||||
|
set := types.New[*application.Application]()
|
||||||
|
|
||||||
|
i.log.Debug().Msgf("scope: %s", in.ResourceScope)
|
||||||
|
query := in.GormResourceFilter(datasource.DBFromCtx(ctx).Model(&application.Application{}))
|
||||||
|
if in.Id != "" {
|
||||||
|
query = query.Where("id = ?", in.Id)
|
||||||
|
}
|
||||||
|
if in.Name != "" {
|
||||||
|
query = query.Where("name = ?", in.Name)
|
||||||
|
}
|
||||||
|
if in.Ready != nil {
|
||||||
|
query = query.Where("ready = ?", *in.Ready)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// DescribeApplication implements application.Service.
|
||||||
|
func (i *ApplicationServiceImpl) DescribeApplication(ctx context.Context, in *application.DescribeApplicationRequest) (*application.Application, error) {
|
||||||
|
query := in.GormResourceFilter(datasource.DBFromCtx(ctx).Model(&application.Application{}))
|
||||||
|
|
||||||
|
ins := &application.Application{}
|
||||||
|
if err := query.Where("id = ?", in.Id).First(ins).Error; err != nil {
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
return nil, exception.NewNotFound("app %s not found", in.Id)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ins, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateApplication implements application.Service.
|
||||||
|
func (i *ApplicationServiceImpl) UpdateApplication(ctx context.Context, in *application.UpdateApplicationRequest) (*application.Application, error) {
|
||||||
|
ins, err := i.DescribeApplication(ctx, &in.DescribeApplicationRequest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ins.CreateApplicationSpec = in.CreateApplicationSpec
|
||||||
|
return ins, datasource.DBFromCtx(ctx).Where("id = ?", in.Id).Updates(ins).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteApplication implements application.Service.
|
||||||
|
func (i *ApplicationServiceImpl) DeleteApplication(ctx context.Context, in *application.DeleteApplicationRequest) (*application.Application, error) {
|
||||||
|
ins, err := i.DescribeApplication(ctx, &in.DescribeApplicationRequest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ins, datasource.DBFromCtx(ctx).
|
||||||
|
Where("id = ?", in.Id).
|
||||||
|
Delete(&application.Application{}).
|
||||||
|
Error
|
||||||
|
}
|
38
devcloud/mpaas/apps/application/impl/application_test.go
Normal file
38
devcloud/mpaas/apps/application/impl/application_test.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package impl_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"122.51.31.227/go-course/go18/devcloud/mpaas/apps/application"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateApplication(t *testing.T) {
|
||||||
|
req := application.NewCreateApplicationRequest()
|
||||||
|
req.Name = "devcloud"
|
||||||
|
req.Description = "应用研发云"
|
||||||
|
req.Type = application.TYPE_SOURCE_CODE
|
||||||
|
req.CodeRepository = application.CodeRepository{
|
||||||
|
SshUrl: "git@122.51.31.227:go-course/go17.git",
|
||||||
|
}
|
||||||
|
req.SetNamespaceId(1)
|
||||||
|
req.SetLabel("team", "dev01.backend_developer")
|
||||||
|
ins, err := svc.CreateApplication(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(ins)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SELECT * FROM `applications` WHERE (NOT JSON_EXTRACT(`label`,'$.team') IS NOT NULL OR JSON_EXTRACT(`label`,'$.team') = 'golang_dev_group_01') ORDER BY created_at desc LIMIT 20
|
||||||
|
func TestQueryApplication(t *testing.T) {
|
||||||
|
req := application.NewQueryApplicationRequest()
|
||||||
|
// dev01.%
|
||||||
|
req.SetNamespaceId(1)
|
||||||
|
req.SetScope("team", []string{"dev01%"})
|
||||||
|
// req.SetScope("env", []string{"prod"})
|
||||||
|
ins, err := svc.QueryApplication(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Log(ins)
|
||||||
|
}
|
37
devcloud/mpaas/apps/application/impl/impl.go
Normal file
37
devcloud/mpaas/apps/application/impl/impl.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package impl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"122.51.31.227/go-course/go18/devcloud/mpaas/apps/application"
|
||||||
|
"github.com/infraboard/mcube/v2/ioc"
|
||||||
|
"github.com/infraboard/mcube/v2/ioc/config/datasource"
|
||||||
|
"github.com/infraboard/mcube/v2/ioc/config/log"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
ioc.Controller().Registry(&ApplicationServiceImpl{})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ application.Service = (*ApplicationServiceImpl)(nil)
|
||||||
|
|
||||||
|
type ApplicationServiceImpl struct {
|
||||||
|
ioc.ObjectImpl
|
||||||
|
|
||||||
|
log *zerolog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ApplicationServiceImpl) Init() error {
|
||||||
|
i.log = log.Sub(i.Name())
|
||||||
|
|
||||||
|
if datasource.Get().AutoMigrate {
|
||||||
|
err := datasource.DB().AutoMigrate(&application.Application{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ApplicationServiceImpl) Name() string {
|
||||||
|
return application.APP_NAME
|
||||||
|
}
|
18
devcloud/mpaas/apps/application/impl/impl_test.go
Normal file
18
devcloud/mpaas/apps/application/impl/impl_test.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package impl_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"122.51.31.227/go-course/go18/devcloud/mpaas/apps/application"
|
||||||
|
"122.51.31.227/go-course/go18/devcloud/mpaas/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
svc application.Service
|
||||||
|
ctx = context.Background()
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
test.DevelopmentSetUp()
|
||||||
|
svc = application.GetService()
|
||||||
|
}
|
@ -3,29 +3,54 @@ package application
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/policy"
|
||||||
|
"github.com/infraboard/mcube/v2/http/request"
|
||||||
|
"github.com/infraboard/mcube/v2/ioc"
|
||||||
"github.com/infraboard/mcube/v2/types"
|
"github.com/infraboard/mcube/v2/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
APP_NAME = "application"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetService() Service {
|
||||||
|
return ioc.Controller().Get(APP_NAME).(Service)
|
||||||
|
}
|
||||||
|
|
||||||
type Service interface {
|
type Service interface {
|
||||||
// 创建应用
|
// 创建应用
|
||||||
CreateApplication(context.Context, CreateApplicationRequest) (*Application, error)
|
CreateApplication(context.Context, *CreateApplicationRequest) (*Application, error)
|
||||||
// 查询应用
|
// 查询应用
|
||||||
QueryApplication(context.Context, QueryApplicationRequest) (*types.Set[*Application], error)
|
QueryApplication(context.Context, *QueryApplicationRequest) (*types.Set[*Application], error)
|
||||||
// 更新应用
|
// 更新应用
|
||||||
UpdateApplication(context.Context, UpdateApplicationRequest) (*Application, error)
|
UpdateApplication(context.Context, *UpdateApplicationRequest) (*Application, error)
|
||||||
// 删除应用
|
// 删除应用
|
||||||
DeleteApplication(context.Context, DeleteApplicationRequest) (*Application, error)
|
DeleteApplication(context.Context, *DeleteApplicationRequest) (*Application, error)
|
||||||
// 获取应用
|
// 获取应用
|
||||||
DescribeApplication(context.Context, DescribeApplicationRequest) (*Application, error)
|
DescribeApplication(context.Context, *DescribeApplicationRequest) (*Application, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewQueryApplicationRequest() *QueryApplicationRequest {
|
||||||
|
return &QueryApplicationRequest{
|
||||||
|
QueryApplicationRequestSpec: QueryApplicationRequestSpec{
|
||||||
|
PageRequest: request.NewDefaultPageRequest(),
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type QueryApplicationRequest struct {
|
type QueryApplicationRequest struct {
|
||||||
|
policy.ResourceScope
|
||||||
|
QueryApplicationRequestSpec
|
||||||
|
}
|
||||||
|
|
||||||
|
type QueryApplicationRequestSpec struct {
|
||||||
|
*request.PageRequest
|
||||||
// 应用ID
|
// 应用ID
|
||||||
Id string `json:"id" bson:"_id"`
|
Id string `json:"id" bson:"_id"`
|
||||||
// 应用名称
|
// 应用名称
|
||||||
Name string `json:"name" bson:"name"`
|
Name string `json:"name" bson:"name"`
|
||||||
// 应用状态
|
// 应用是否就绪
|
||||||
Status string `json:"status" bson:"status"`
|
Ready *bool `json:"ready" bson:"ready"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateApplicationRequest struct {
|
type UpdateApplicationRequest struct {
|
||||||
@ -40,6 +65,7 @@ type DeleteApplicationRequest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type DescribeApplicationRequest struct {
|
type DescribeApplicationRequest struct {
|
||||||
|
policy.ResourceScope
|
||||||
// 应用ID
|
// 应用ID
|
||||||
Id string `json:"id" bson:"_id"`
|
Id string `json:"id" bson:"_id"`
|
||||||
}
|
}
|
||||||
|
@ -1,92 +1,188 @@
|
|||||||
package application
|
package application
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/policy"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/infraboard/mcube/v2/ioc/config/validator"
|
||||||
"github.com/infraboard/mcube/v2/tools/pretty"
|
"github.com/infraboard/mcube/v2/tools/pretty"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func NewApplication(req CreateApplicationRequest) (*Application, error) {
|
||||||
|
app := &Application{
|
||||||
|
CreateApplicationRequest: req,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := app.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 动态计算评审状态
|
||||||
|
if len(req.Audits) == 0 {
|
||||||
|
app.SetReady(true)
|
||||||
|
} else {
|
||||||
|
app.SetReady(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
app.BuildId()
|
||||||
|
return app, nil
|
||||||
|
}
|
||||||
|
|
||||||
type Application struct {
|
type Application struct {
|
||||||
// 对象Id
|
// 对象Id
|
||||||
Id string `json:"id" bson:"_id"`
|
Id string `json:"id" bson:"_id" gorm:"column:id;primary_key"`
|
||||||
// 更新时间
|
// 更新时间
|
||||||
UpdateAt time.Time `json:"update_at" bson:"update_at"`
|
UpdateAt *time.Time `json:"update_at" bson:"update_at" gorm:"column:update_at"`
|
||||||
// 更新人
|
// 更新人
|
||||||
UpdateBy string `json:"update_by" bson:"update_by"`
|
UpdateBy string `json:"update_by" bson:"update_by" gorm:"column:update_by"`
|
||||||
// 创建请求
|
// 创建请求
|
||||||
CreateApplicationRequest
|
CreateApplicationRequest
|
||||||
|
// 应用状态
|
||||||
|
ApplicationStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Application) TableName() string {
|
||||||
|
return "applications"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Application) String() string {
|
func (a *Application) String() string {
|
||||||
return pretty.ToJSON(a)
|
return pretty.ToJSON(a)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *Application) SetReady(v bool) *Application {
|
||||||
|
a.Ready = &v
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Application) BuildId() {
|
||||||
|
bf := bytes.NewBuffer([]byte{})
|
||||||
|
|
||||||
|
switch a.Type {
|
||||||
|
case TYPE_SOURCE_CODE:
|
||||||
|
bf.WriteString(a.CodeRepository.SshUrl)
|
||||||
|
case TYPE_CONTAINER_IMAGE:
|
||||||
|
bf.WriteString(a.GetImageRepositoryPrimaryAddress())
|
||||||
|
}
|
||||||
|
|
||||||
|
a.Id = uuid.NewSHA1(uuid.Nil, bf.Bytes()).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCreateApplicationRequest() *CreateApplicationRequest {
|
||||||
|
return &CreateApplicationRequest{
|
||||||
|
CreateAt: time.Now(),
|
||||||
|
CreateApplicationSpec: CreateApplicationSpec{
|
||||||
|
Extras: map[string]string{},
|
||||||
|
ImageRepository: []ImageRepository{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type CreateApplicationRequest struct {
|
type CreateApplicationRequest struct {
|
||||||
// 创建人
|
// 创建人
|
||||||
CreateBy string `json:"create_by" bson:"create_by" description:"创建人"`
|
CreateBy string `json:"create_by" bson:"create_by" gorm:"column:create_by" description:"创建人"`
|
||||||
// 创建时间
|
// 创建时间
|
||||||
CreateAt time.Time `json:"create_at" bson:"create_at"`
|
CreateAt time.Time `json:"create_at" bson:"create_at" gorm:"column:create_at" description:"创建时间"`
|
||||||
// 应用所属空间名称
|
// 资源范围, Namespace是继承的, Scope是API添加的
|
||||||
Namespace string `json:"namespace" bson:"namespace" description:"应用所属空间名称"`
|
policy.ResourceLabel
|
||||||
// 应用创建参数
|
// 应用创建参数
|
||||||
CreateApplicationSpec
|
CreateApplicationSpec
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *CreateApplicationRequest) Validate() error {
|
||||||
|
return validator.Validate(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *CreateApplicationRequest) GetImageRepositoryPrimaryAddress() string {
|
||||||
|
for _, repo := range a.ImageRepository {
|
||||||
|
if repo.IsPrimary {
|
||||||
|
return repo.Address
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
type CreateApplicationSpec struct {
|
type CreateApplicationSpec struct {
|
||||||
// 该应用是否已经准备就绪
|
|
||||||
Ready bool `json:"ready" bson:"ready" description:"该应用是否已经准备就绪"`
|
|
||||||
// 应用名称
|
// 应用名称
|
||||||
Name string `json:"name" bson:"name" description:"应用名称"`
|
Name string `json:"name" bson:"name" gorm:"column:name" description:"应用名称"`
|
||||||
// 应用描述
|
// 应用描述
|
||||||
Description string `json:"description" bson:"description" description:"应用描述"`
|
Description string `json:"description" bson:"description" gorm:"column:description" description:"应用描述"`
|
||||||
// 应用图标
|
// 应用图标
|
||||||
Icon string `json:"icon" bson:"icon" description:"应用图标"`
|
Icon string `json:"icon" bson:"icon" gorm:"column:icons" description:"应用图标"`
|
||||||
// 应用类型
|
// 应用类型
|
||||||
Type TYPE `json:"type" bson:"type" description:"应用类型, SOURCE_CODE, CONTAINER_IMAGE, OTHER"`
|
Type TYPE `json:"type" bson:"type" gorm:"column:type" description:"应用类型, SOURCE_CODE, CONTAINER_IMAGE, OTHER"`
|
||||||
// 应用代码仓库信息
|
// 应用代码仓库信息
|
||||||
CodeRepository CodeRepository `json:"code_repository" bson:"code_repository" description:"应用代码仓库信息"`
|
CodeRepository CodeRepository `json:"code_repository" bson:",inline" gorm:"embedded" description:"应用代码仓库信息"`
|
||||||
// 应用镜像仓库信息
|
// 应用镜像仓库信息
|
||||||
ImageRepository ImageRepository `json:"image_repository" bson:"image_repository" description:"应用镜像仓库信息"`
|
ImageRepository []ImageRepository `json:"image_repository" gorm:"column:image_repository;type:json;serializer:json;" bson:"image_repository" description:"应用镜像仓库信息"`
|
||||||
// 应用所有者
|
// 应用所有者
|
||||||
Owner string `json:"owner" bson:"owner" description:"应用所有者"`
|
Owner string `json:"owner" bson:"owner" gorm:"column:owner" description:"应用所有者"`
|
||||||
// 应用等级, 评估这个应用的重要程度
|
// 应用等级, 评估这个应用的重要程度
|
||||||
Level uint32 `json:"level" bson:"level" description:"应用等级, 评估这个应用的重要程度"`
|
Level *uint32 `json:"level" bson:"level" gorm:"column:level" description:"应用等级, 评估这个应用的重要程度"`
|
||||||
// 应用优先级, 应用启动的先后顺序
|
// 应用优先级, 应用启动的先后顺序
|
||||||
Priority uint32 `json:"priority" bson:"priority" description:"应用优先级, 应用启动的先后顺序"`
|
Priority *uint32 `json:"priority" bson:"priority" gorm:"column:priority" description:"应用优先级, 应用启动的先后顺序"`
|
||||||
// 应用标签
|
// 额外的其他属性
|
||||||
Labels map[string]string `json:"labels" bson:"labels" description:"应用标签"`
|
Extras map[string]string `json:"extras" form:"extras" bson:"extras" gorm:"column:extras;type:json;serializer:json;"`
|
||||||
|
|
||||||
|
// 指定应用的评审方
|
||||||
|
Audits []ApplicationReadyAudit `json:"audits" bson:"audits" gorm:"column:audits;type:json;serializer:json" description:"参与应用准备就绪的评审方"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 服务代码仓库信息
|
// 服务代码仓库信息
|
||||||
type CodeRepository struct {
|
type CodeRepository struct {
|
||||||
// 仓库提供商
|
// 仓库提供商
|
||||||
Provider SCM_PROVIDER `json:"provider" bson:"provider"`
|
Provider SCM_PROVIDER `json:"provider" bson:"provider" gorm:"column:provider"`
|
||||||
// token 操作仓库, 比如设置回调
|
// token 操作仓库, 比如设置回调
|
||||||
Token string `json:"token" bson:"token"`
|
Token string `json:"token" bson:"token" gorm:"column:token"`
|
||||||
// 仓库对应的项目Id
|
// 仓库对应的项目Id
|
||||||
ProjectId string `json:"project_id" bson:"project_id"`
|
ProjectId string `json:"project_id" bson:"project_id" gorm:"column:project_id"`
|
||||||
// 仓库对应空间
|
// 仓库对应空间
|
||||||
Namespace string `json:"namespace" bson:"namespace"`
|
Namespace string `json:"namespace" bson:"namespace" gorm:"column:namespace"`
|
||||||
// 仓库web url地址
|
// 仓库web url地址
|
||||||
WebUrl string `json:"web_url" bson:"web_url"`
|
WebUrl string `json:"web_url" bson:"web_url" gorm:"column:web_url"`
|
||||||
// 仓库ssh url地址
|
// 仓库ssh url地址
|
||||||
SshUrl string `json:"ssh_url" bson:"ssh_url"`
|
SshUrl string `json:"ssh_url" bson:"ssh_url" gorm:"column:ssh_url"`
|
||||||
// 仓库http url地址
|
// 仓库http url地址
|
||||||
HttpUrl string `json:"http_url" bson:"http_url"`
|
HttpUrl string `json:"http_url" bson:"http_url" gorm:"column:http_url"`
|
||||||
// 源代码使用的编程语言, 构建时, 不同语言有不同的构建环境
|
// 源代码使用的编程语言, 构建时, 不同语言有不同的构建环境
|
||||||
Language *LANGUAGE `json:"language" bson:"language"`
|
Language *LANGUAGE `json:"language" bson:"language" gorm:"column:language"`
|
||||||
// 开启Hook设置
|
// 开启Hook设置
|
||||||
EnableHook bool `json:"enable_hook" bson:"enable_hook"`
|
EnableHook bool `json:"enable_hook" bson:"enable_hook" gorm:"column:enable_hook"`
|
||||||
// Hook设置
|
// Hook设置
|
||||||
HookConfig string `json:"hook_config" bson:"hook_config"`
|
HookConfig string `json:"hook_config" bson:"hook_config" gorm:"column:hook_config"`
|
||||||
// scm设置Hook后返回的id, 用于删除应用时,取消hook使用
|
// scm设置Hook后返回的id, 用于删除应用时,取消hook使用
|
||||||
HookId string `json:"hook_id" bson:"hook_id"`
|
HookId string `json:"hook_id" bson:"hook_id" gorm:"column:hook_id"`
|
||||||
// 仓库的创建时间
|
// 仓库的创建时间
|
||||||
CreatedAt time.Time `json:"created_at" bson:"created_at"`
|
CreatedAt *time.Time `json:"created_at" bson:"created_at" gorm:"column:created_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 镜像仓库
|
// 镜像仓库
|
||||||
type ImageRepository struct {
|
type ImageRepository struct {
|
||||||
// 服务镜像地址
|
// 服务镜像地址, 比如 gcr.lank8s.cn/kaniko-project/executor
|
||||||
Address string `json:"address" bson:"address"`
|
Address string `json:"address" bson:"address"`
|
||||||
|
// 是不是主仓库
|
||||||
|
IsPrimary bool `json:"is_primary" bson:"is_primary"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ApplicationStatus struct {
|
||||||
|
// 该应用是否已经准备就绪,多方确认的一个过程后计算出来的
|
||||||
|
Ready *bool `json:"ready" bson:"ready" gorm:"column:ready" description:"该应用是否已经准备就绪"`
|
||||||
|
// 就绪状态修改时间
|
||||||
|
UpdateAt *time.Time `json:"ready_update_at" bson:"ready_update_at" gorm:"column:ready_update_at" description:"就绪状态修改时间"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 参与应用准备就绪的评审方
|
||||||
|
type ApplicationReadyAudit struct {
|
||||||
|
// 评审角色, Dev, Test, Ops
|
||||||
|
RoleName string `json:"role_name"`
|
||||||
|
// 评审人
|
||||||
|
AuditBy string `json:"audit_by"`
|
||||||
|
// 评审时间
|
||||||
|
AuditAt time.Time `json:"audit_at"`
|
||||||
|
// 是否就绪
|
||||||
|
Ready bool `json:"ready"`
|
||||||
|
// 评审建议
|
||||||
|
Message string `json:"message"`
|
||||||
}
|
}
|
||||||
|
@ -1 +1,6 @@
|
|||||||
package apps
|
package apps
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "122.51.31.227/go-course/go18/devcloud/mpaas/apps/application/api"
|
||||||
|
_ "122.51.31.227/go-course/go18/devcloud/mpaas/apps/application/impl"
|
||||||
|
)
|
||||||
|
84
devcloud/mpaas/design.drawio
Normal file
84
devcloud/mpaas/design.drawio
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<mxfile host="65bd71144e">
|
||||||
|
<diagram id="l8amJ-V2lY7reZdTNxwa" name="第 1 页">
|
||||||
|
<mxGraphModel dx="888" dy="514" 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="edgeStyle=none;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" edge="1" parent="1" source="3" target="7">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="3" value="artifact<div>制品库</div>" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="259" y="70" width="120" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="4" 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="5" target="3">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="5" value="CI工具" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="29" y="70" width="120" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="6" style="edgeStyle=none;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="7" target="11">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="7" value="deploy<div>应用部署</div>" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="259" y="220" width="120" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="8" 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="10" target="7">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="9" value="选择适合的制品格式<div><br></div>" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="8">
|
||||||
|
<mxGeometry x="0.2278" y="-3" relative="1" as="geometry">
|
||||||
|
<mxPoint x="29" y="-17" as="offset"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="10" value="cluster<div>应用集群</div>" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="589" y="220" width="120" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="11" value="k8s 集群" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="259" y="400" width="120" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="12" 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="16" target="11">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="13" value="watch" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="12">
|
||||||
|
<mxGeometry x="0.2242" y="-1" relative="1" as="geometry">
|
||||||
|
<mxPoint as="offset"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="14" style="edgeStyle=none;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" source="16" target="10">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="15" value="实时更新集群状态" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="14">
|
||||||
|
<mxGeometry x="0.0236" y="-4" relative="1" as="geometry">
|
||||||
|
<mxPoint as="offset"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="16" value="sync<div>部署同步</div>" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="589" y="400" width="120" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="17" value="首次部署使用模版渲染" style="edgeStyle=none;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="18" target="10">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="18" value="temlate<div>部署模版</div>" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="589" y="70" width="120" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="19" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0;exitY=0.3333333333333333;exitDx=0;exitDy=0;exitPerimeter=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="20" target="10">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="20" value="Actor" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="770" y="60" width="30" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="22" 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="21" target="10">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="23" value="应用部署" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="22">
|
||||||
|
<mxGeometry x="-0.3768" y="2" relative="1" as="geometry">
|
||||||
|
<mxPoint as="offset"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="21" value="应用" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="870" y="220" width="120" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
</mxfile>
|
1
devcloud/mpaas/registry.go
Normal file
1
devcloud/mpaas/registry.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package mpaas
|
18
devcloud/mpaas/test/set_up.go
Normal file
18
devcloud/mpaas/test/set_up.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/infraboard/mcube/v2/ioc"
|
||||||
|
// 要注册哪些对象, Book, Comment
|
||||||
|
|
||||||
|
// 加载的业务对象
|
||||||
|
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps"
|
||||||
|
_ "122.51.31.227/go-course/go18/devcloud/mpaas/apps"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DevelopmentSetUp() {
|
||||||
|
// import 后自动执行的逻辑
|
||||||
|
// 工具对象的初始化, 需要的是绝对路径
|
||||||
|
ioc.DevelopmentSetupWithPath(os.Getenv("CONFIG_PATH"))
|
||||||
|
}
|
17
go.mod
17
go.mod
@ -8,20 +8,25 @@ require (
|
|||||||
github.com/emicklei/go-restful/v3 v3.12.2
|
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/google/uuid v1.6.0
|
||||||
github.com/infraboard/mcube/v2 v2.0.61
|
github.com/infraboard/devops v0.0.6
|
||||||
github.com/infraboard/modules v0.0.12
|
github.com/infraboard/mcube/v2 v2.0.63
|
||||||
|
github.com/infraboard/modules v0.0.20
|
||||||
github.com/rs/xid v1.6.0
|
github.com/rs/xid v1.6.0
|
||||||
github.com/rs/zerolog v1.34.0
|
github.com/rs/zerolog v1.34.0
|
||||||
github.com/segmentio/kafka-go v0.4.47
|
github.com/segmentio/kafka-go v0.4.47
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1138
|
||||||
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/lighthouse v1.0.1134
|
||||||
go.mongodb.org/mongo-driver v1.17.3
|
go.mongodb.org/mongo-driver v1.17.3
|
||||||
golang.org/x/crypto v0.38.0
|
golang.org/x/crypto v0.38.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
|
gorm.io/datatypes v1.2.5
|
||||||
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
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/bytedance/sonic v1.13.2 // indirect
|
github.com/bytedance/sonic v1.13.2 // indirect
|
||||||
@ -29,7 +34,7 @@ require (
|
|||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||||
github.com/gin-contrib/sse v1.0.0 // indirect
|
github.com/gin-contrib/sse v1.0.0 // indirect
|
||||||
@ -44,13 +49,13 @@ require (
|
|||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
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.8.1 // indirect
|
||||||
github.com/goccy/go-json v0.10.5 // indirect
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // 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
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect
|
||||||
github.com/jackc/pgx/v5 v5.5.5 // indirect
|
github.com/jackc/pgx/v5 v5.5.5 // indirect
|
||||||
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
@ -77,6 +82,7 @@ require (
|
|||||||
github.com/prometheus/common v0.63.0 // indirect
|
github.com/prometheus/common v0.63.0 // indirect
|
||||||
github.com/prometheus/procfs v0.16.0 // indirect
|
github.com/prometheus/procfs v0.16.0 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
|
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||||
github.com/spf13/cobra v1.9.1 // indirect
|
github.com/spf13/cobra v1.9.1 // indirect
|
||||||
github.com/spf13/pflag v1.0.6 // indirect
|
github.com/spf13/pflag v1.0.6 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
@ -114,5 +120,6 @@ require (
|
|||||||
modernc.org/mathutil v1.5.0 // indirect
|
modernc.org/mathutil v1.5.0 // indirect
|
||||||
modernc.org/memory v1.5.0 // indirect
|
modernc.org/memory v1.5.0 // indirect
|
||||||
modernc.org/sqlite v1.23.1 // indirect
|
modernc.org/sqlite v1.23.1 // indirect
|
||||||
|
resty.dev/v3 v3.0.0-beta.2 // indirect
|
||||||
sigs.k8s.io/yaml v1.4.0 // indirect
|
sigs.k8s.io/yaml v1.4.0 // indirect
|
||||||
)
|
)
|
||||||
|
45
go.sum
45
go.sum
@ -1,3 +1,5 @@
|
|||||||
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
@ -20,8 +22,9 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
|
|||||||
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/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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/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 h1:Ur+yGxoOH/7KRmcj/UoMFqC3VeNc9VOe+/XidumxTvk=
|
||||||
@ -66,11 +69,16 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
|
|||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
|
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
|
||||||
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||||
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
|
||||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||||
|
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||||
|
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
|
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
|
||||||
|
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||||
|
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
|
||||||
|
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||||
@ -87,14 +95,16 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0Ntos
|
|||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
github.com/infraboard/mcube/v2 v2.0.61 h1:al8Z+poXXOjfTIAY48ujFzV1uYzH/N7/xmve/ZXArbo=
|
github.com/infraboard/devops v0.0.6 h1:oo7RfRBxu9hbI/+bYzXuX40BfG1hRyVtxC0fBhoFGBU=
|
||||||
github.com/infraboard/mcube/v2 v2.0.61/go.mod h1:TbYs8cnD8Cg19sTdU0D+vqWAN+LzoxhMYWmAC2pfJkQ=
|
github.com/infraboard/devops v0.0.6/go.mod h1:Ac+W3wFy5pG9EH7f1W8DxiAZRhnTDFHrp27WKkYnNoU=
|
||||||
github.com/infraboard/modules v0.0.12 h1:vQqm+JwzmhL+hcD9SV+WVlp9ecInc7NsbGahcTmJ0Wk=
|
github.com/infraboard/mcube/v2 v2.0.63 h1:OUmDajfLAQ1LUVEutCSRbDIQItyLSPHYWXUJLO6IbaE=
|
||||||
github.com/infraboard/modules v0.0.12/go.mod h1:NdgdH/NoeqibJmFPn9th+tisMuR862/crbXeH4FPMaU=
|
github.com/infraboard/mcube/v2 v2.0.63/go.mod h1:TbYs8cnD8Cg19sTdU0D+vqWAN+LzoxhMYWmAC2pfJkQ=
|
||||||
|
github.com/infraboard/modules v0.0.20 h1:5tNT9eSkTBCx4OA+7sZr1Bf+miOGf9Zft2X/sESrlpE=
|
||||||
|
github.com/infraboard/modules v0.0.20/go.mod h1:H2kRn3Ksp+3OMt9yYB2imNR2RJgSytTVt9WFP8XpDRQ=
|
||||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
|
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
|
||||||
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
|
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
|
||||||
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||||
@ -136,6 +146,10 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
|
|||||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
|
github.com/microsoft/go-mssqldb v1.7.2 h1:CHkFJiObW7ItKTJfHo1QX7QBBD1iV+mn1eOyRP3b/PA=
|
||||||
|
github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA=
|
||||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
@ -167,6 +181,8 @@ github.com/prometheus/procfs v0.16.0/go.mod h1:8veyXUu3nGP7oaCxhX6yeaM5u4stL2FeM
|
|||||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||||
@ -193,6 +209,11 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
|
|||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1134/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||||
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1138 h1:eMVp9kzjBptP3K0xaUUS68dF5nNTFLbom3uQREaqftM=
|
||||||
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1138/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||||
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/lighthouse v1.0.1134 h1:+pjecViStkJrHVbZs+OtCFULfAoD0UOxAZJVDwBjKC8=
|
||||||
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/lighthouse v1.0.1134/go.mod h1:vp2EZhERa7VxNFUlntLv7JDu3OD/pbpUpduFf5BGSfQ=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||||
@ -317,10 +338,16 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/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/datatypes v1.2.5 h1:9UogU3jkydFVW1bIVVeoYsTpLRgwDVW3rHfJG6/Ek9I=
|
||||||
|
gorm.io/datatypes v1.2.5/go.mod h1:I5FUdlKpLb5PMqeMQhm30CQ6jXP8Rj89xkTeCSAaAD4=
|
||||||
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
|
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
|
||||||
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
|
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
|
||||||
gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314=
|
gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314=
|
||||||
gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
|
gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
|
||||||
|
gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU=
|
||||||
|
gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
|
||||||
|
gorm.io/driver/sqlserver v1.5.4 h1:xA+Y1KDNspv79q43bPyjDMUgHoYHLhXYmdFcYPobg8g=
|
||||||
|
gorm.io/driver/sqlserver v1.5.4/go.mod h1:+frZ/qYmuna11zHPlh5oc2O6ZA/lS88Keb0XSH1Zh/g=
|
||||||
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||||
gorm.io/gorm v1.26.0 h1:9lqQVPG5aNNS6AyHdRiwScAVnXHg/L/Srzx55G5fOgs=
|
gorm.io/gorm v1.26.0 h1:9lqQVPG5aNNS6AyHdRiwScAVnXHg/L/Srzx55G5fOgs=
|
||||||
gorm.io/gorm v1.26.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
gorm.io/gorm v1.26.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||||
@ -333,5 +360,7 @@ modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
|||||||
modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
|
modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
|
||||||
modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
|
modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
|
||||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||||
|
resty.dev/v3 v3.0.0-beta.2 h1:xu4mGAdbCLuc3kbk7eddWfWm4JfhwDtdapwss5nCjnQ=
|
||||||
|
resty.dev/v3 v3.0.0-beta.2/go.mod h1:OgkqiPvTDtOuV4MGZuUDhwOpkY8enjOsjjMzeOHefy4=
|
||||||
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
||||||
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
||||||
|
3
skills/html/README.md
Normal file
3
skills/html/README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# HTML
|
||||||
|
|
||||||
|
vscode 插件: open in browser
|
BIN
skills/html/images/1.jpeg
Normal file
BIN
skills/html/images/1.jpeg
Normal file
Binary file not shown.
After Width: | Height: | Size: 181 KiB |
BIN
skills/html/images/2.jpeg
Normal file
BIN
skills/html/images/2.jpeg
Normal file
Binary file not shown.
After Width: | Height: | Size: 85 KiB |
39
skills/html/index.css
Normal file
39
skills/html/index.css
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/* p {
|
||||||
|
color: blue;
|
||||||
|
} */
|
||||||
|
|
||||||
|
/* . class */
|
||||||
|
.red {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* # 表示这是一个id选择器 */
|
||||||
|
#input01 {
|
||||||
|
width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ul>li:first-child {
|
||||||
|
font-weight: 600;
|
||||||
|
} */
|
||||||
|
|
||||||
|
|
||||||
|
ul li:hover {
|
||||||
|
color: red;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 220px;
|
||||||
|
height: 220px;
|
||||||
|
object-fit: cover; /* 裁剪成正方形 */
|
||||||
|
border-radius: 4px; /* 可选:圆角 */
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-warppter {
|
||||||
|
height: 300px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
109
skills/html/index.html
Normal file
109
skills/html/index.html
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Document</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="./index.css" />
|
||||||
|
<!-- <style>
|
||||||
|
p {
|
||||||
|
color: blue;
|
||||||
|
}
|
||||||
|
</style> -->
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1 id="title01">HTML 语法介绍</h1>
|
||||||
|
<p class="red">段落</p>
|
||||||
|
<p>段落</p>
|
||||||
|
<span class="red">文字A</span>
|
||||||
|
<br />
|
||||||
|
<span class="red">文字B</span>
|
||||||
|
<br />
|
||||||
|
<form action="demo_form.php" method="get">
|
||||||
|
First name: <input id="input01" type="text" name="fname" /><br />
|
||||||
|
Last name: <input type="text" name="lname" /><br />
|
||||||
|
<input type="submit" value="提交" />
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<iframe
|
||||||
|
src="https://www.baidu.com"
|
||||||
|
style="width: 800px; height: 400"
|
||||||
|
></iframe>
|
||||||
|
|
||||||
|
<table border="1">
|
||||||
|
<tr>
|
||||||
|
<th>Month</th>
|
||||||
|
<th>Savings</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>January</td>
|
||||||
|
<td>$100</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>February</td>
|
||||||
|
<td>$80</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div style="width: 400px; height: 400; background-color: aqua">
|
||||||
|
<table border="1">
|
||||||
|
<tr>
|
||||||
|
<th>Month</th>
|
||||||
|
<th>Savings</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>January</td>
|
||||||
|
<td>$100</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>February</td>
|
||||||
|
<td>$80</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<br />
|
||||||
|
<p>这是一个段落</p>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div style="width: 400px; height: 400; background-color: aqua">
|
||||||
|
<table border="1">
|
||||||
|
<tr>
|
||||||
|
<th>Month</th>
|
||||||
|
<th>Savings</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>January</td>
|
||||||
|
<td>$100</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>February</td>
|
||||||
|
<td>$80</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<br />
|
||||||
|
<p>这是一个段落</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span>span1</span>
|
||||||
|
<span>span2</span>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<ul id="list_menu" class="ul_class">
|
||||||
|
<li id="coffee">Coffee</li>
|
||||||
|
<li>Tea</li>
|
||||||
|
<li>Milk</li>
|
||||||
|
<div>
|
||||||
|
<li>绿茶</li>
|
||||||
|
<li>红茶</li>
|
||||||
|
</div>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="img-warppter">
|
||||||
|
<img src="./images/1.jpeg" alt="" />
|
||||||
|
<img src="./images/2.jpeg" alt="" />
|
||||||
|
<img src="./images/1.jpeg" alt="" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.getElementById("title01").style.color = "red";
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
5
skills/javascript/README.md
Normal file
5
skills/javascript/README.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# js
|
||||||
|
|
||||||
|
[课件](https://gitee.com/infraboard/go-course/blob/master/day19/javascript.md)
|
||||||
|
|
||||||
|
安装Code Runner插件
|
16
skills/javascript/app.mjs
Normal file
16
skills/javascript/app.mjs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// 唯一的全局变量MYAPP:
|
||||||
|
var MYAPP = {};
|
||||||
|
|
||||||
|
// 其他变量:
|
||||||
|
MYAPP.name = 'myapp';
|
||||||
|
MYAPP.version = 1.0;
|
||||||
|
|
||||||
|
// 其他函数:
|
||||||
|
MYAPP.foo = function () {
|
||||||
|
return 'foo';
|
||||||
|
};
|
||||||
|
|
||||||
|
// export {MYAPP}
|
||||||
|
|
||||||
|
// MYAPP --> default
|
||||||
|
export default MYAPP
|
57
skills/javascript/async.drawio
Normal file
57
skills/javascript/async.drawio
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<mxfile host="65bd71144e">
|
||||||
|
<diagram id="0WS3t2Fnvb50-m555Ywn" name="第 1 页">
|
||||||
|
<mxGraphModel dx="1176" dy="389" 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="180" y="70" width="40" height="410" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="3" value="fna" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="240" y="90" width="120" height="50" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="4" value="fnb" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="360" y="120" width="120" height="50" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="5" value="fnc" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="480" y="140" width="120" height="50" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="6" value="fnd" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="600" y="160" width="120" height="50" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="7" value="async fna" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="280" y="270" width="120" height="50" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="8" value="async fnb" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="280" y="350" width="120" height="50" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="9" value="async fnc" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="280" y="430" width="120" height="50" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="10" value="async fnd" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="280" y="510" width="120" height="50" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="13" style="edgeStyle=none;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" source="11" target="7">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="16" value="await" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="13">
|
||||||
|
<mxGeometry x="0.3011" y="1" relative="1" as="geometry">
|
||||||
|
<mxPoint as="offset"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="15" style="edgeStyle=none;html=1;exitX=0;exitY=0.75;exitDx=0;exitDy=0;entryX=1;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" source="11" target="8">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="11" value="excutor(<div>aysnc executor</div><div><br></div><div>event pool)</div>" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="591" y="270" width="120" height="290" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="12" style="edgeStyle=none;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0.017;entryY=0.31;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="7" target="11">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="14" style="edgeStyle=none;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=-0.025;entryY=0.652;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="8" target="11">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
</mxfile>
|
69
skills/javascript/promise.js
Normal file
69
skills/javascript/promise.js
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
function testApiCall(success, failed) {
|
||||||
|
var timeOut = Math.random() * 2;
|
||||||
|
console.log('set timeout to: ' + timeOut + ' seconds.');
|
||||||
|
setTimeout(function () {
|
||||||
|
if (timeOut < 1) {
|
||||||
|
console.log('call resolve()...');
|
||||||
|
success('200 OK');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log('call reject()...');
|
||||||
|
failed('timeout in ' + timeOut + ' seconds.');
|
||||||
|
}
|
||||||
|
}, timeOut * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通过回调实现的异步方案
|
||||||
|
// fna()
|
||||||
|
// fnb()
|
||||||
|
// fnc()
|
||||||
|
console.log('start ...')
|
||||||
|
testApiCall(
|
||||||
|
// success callback
|
||||||
|
(data) => {
|
||||||
|
console.log(data)
|
||||||
|
},
|
||||||
|
// failed callback
|
||||||
|
(error) => {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
console.log('end ...')
|
||||||
|
|
||||||
|
// fna(fnb_sucess(fnc_success(fnd_success)), fnb_error(fnc_error(fnd_error)))
|
||||||
|
|
||||||
|
// promise 封装技术, 他让异步成为一种标准, 使用promise的标准方式来处理异常
|
||||||
|
// promise 像一个接口,他约束了 excutor对象的 实现方式
|
||||||
|
var p1 = new Promise(testApiCall)
|
||||||
|
// p1.then((data) => {
|
||||||
|
// console.log(data)
|
||||||
|
// // p2.then().catch().finally()
|
||||||
|
// }).catch((error) => {
|
||||||
|
// console.log(error)
|
||||||
|
// }).finally(() => {
|
||||||
|
// console.log('finnal')
|
||||||
|
// })
|
||||||
|
|
||||||
|
|
||||||
|
// 异步等待
|
||||||
|
// 以同步编程的方式,来实现异步代码
|
||||||
|
var async_await = async () => {
|
||||||
|
try {
|
||||||
|
const resp = await p1
|
||||||
|
console.log(resp)
|
||||||
|
|
||||||
|
// const res2 = await p2
|
||||||
|
// console.log(resp2)
|
||||||
|
|
||||||
|
// const res3 = await p3
|
||||||
|
// console.log(resp2)
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async_await()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
84
skills/javascript/test.js
Normal file
84
skills/javascript/test.js
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
// function sum (x , y) {
|
||||||
|
// return x + y
|
||||||
|
// }
|
||||||
|
|
||||||
|
// console.log(sum(1, 10))
|
||||||
|
|
||||||
|
// var person = {name: '小明', age: 23}
|
||||||
|
|
||||||
|
// console.log(person)
|
||||||
|
|
||||||
|
// person.greet = function() {
|
||||||
|
// console.log(`hello, my name is ${this.name}, age ${this.age}`)
|
||||||
|
// }
|
||||||
|
// person.greet()
|
||||||
|
|
||||||
|
|
||||||
|
// var name = '李四'
|
||||||
|
// var age = 23
|
||||||
|
|
||||||
|
// function greet () {
|
||||||
|
// console.log(`hello, my name is ${name}, age ${age}`)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// greet()
|
||||||
|
|
||||||
|
|
||||||
|
// fn = function (x , y) {
|
||||||
|
// return x + y
|
||||||
|
// }
|
||||||
|
// console.log(fn(1,2))
|
||||||
|
|
||||||
|
|
||||||
|
// fn = (x, y) => {
|
||||||
|
// return x + y
|
||||||
|
// }
|
||||||
|
// console.log(fn(1,2))
|
||||||
|
|
||||||
|
// fn = (x, y) => { return x + y}
|
||||||
|
// console.log(fn(1,2))
|
||||||
|
|
||||||
|
// fn = (x, y) => x + y
|
||||||
|
// console.log(fn(1,2))
|
||||||
|
|
||||||
|
// pow = x => x * x
|
||||||
|
|
||||||
|
// console.log(pow(100))
|
||||||
|
|
||||||
|
// pkg
|
||||||
|
import {firstName, sum as my_sum} from './tool.mjs'
|
||||||
|
console.log(my_sum(1,10))
|
||||||
|
console.log(firstName)
|
||||||
|
|
||||||
|
// pkg.
|
||||||
|
// import { MYAPP } from './app.mjs'
|
||||||
|
// console.log(MYAPP.version)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// pkg
|
||||||
|
// import { default as MYAPP } from './app.mjs'
|
||||||
|
import appPKg from './app.mjs'
|
||||||
|
console.log(appPKg.name)
|
||||||
|
|
||||||
|
// for item := rang a {}
|
||||||
|
var a = ['A', 'B', 'C'];
|
||||||
|
for (var item of a) {
|
||||||
|
// fn(item)
|
||||||
|
console.log(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
// for k,v := range o {}
|
||||||
|
var o = {
|
||||||
|
name: 'Jack',
|
||||||
|
age: 20,
|
||||||
|
city: 'Beijing'
|
||||||
|
};
|
||||||
|
for (var item of Object.keys(o)) {
|
||||||
|
console.log(item, o[item])
|
||||||
|
}
|
||||||
|
|
||||||
|
// forEach
|
||||||
|
a.forEach((item) => {
|
||||||
|
console.log(item)
|
||||||
|
})
|
10
skills/javascript/tool.mjs
Normal file
10
skills/javascript/tool.mjs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
// export var sum = (x, y) => {return x + y}
|
||||||
|
export function sum(x, y) {
|
||||||
|
return x + y
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstName = 'Michael';
|
||||||
|
var lastName = 'Jackson';
|
||||||
|
var year = 1958;
|
||||||
|
|
||||||
|
export {firstName, lastName, year}
|
@ -116,7 +116,7 @@ func TestReadMessage(t *testing.T) {
|
|||||||
GroupID: "devcloud-go18-audit",
|
GroupID: "devcloud-go18-audit",
|
||||||
// 可以指定Partition消费消息
|
// 可以指定Partition消费消息
|
||||||
// Partition: 0,
|
// Partition: 0,
|
||||||
Topic: "audit_go18",
|
Topic: "task_run_events",
|
||||||
MinBytes: 10e3, // 10KB
|
MinBytes: 10e3, // 10KB
|
||||||
MaxBytes: 10e6, // 10MB
|
MaxBytes: 10e6, // 10MB
|
||||||
})
|
})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user