Compare commits

...

7 Commits

Author SHA1 Message Date
15f32e0e30 补充css 2025-07-20 17:26:00 +08:00
066b9f7ad2 补充async await 2025-07-20 14:48:17 +08:00
42bb490180 补充 for 2025-07-20 11:59:18 +08:00
c1f7ce2b85 update kafka 2025-07-20 09:10:36 +08:00
87bda92fac 补充资源同步 2025-07-12 11:39:37 +08:00
ba04b02cf8 补充对接Task模块 2025-07-06 18:10:36 +08:00
0abb789a44 update cmdb 2025-07-06 08:43:20 +08:00
43 changed files with 1502 additions and 19 deletions

View File

@ -58,7 +58,6 @@ func (i *consumer) Priority() int {
return event.PRIORITY - 1
}
func (i *consumer) Close(ctx context.Context) error {
func (i *consumer) Close(ctx context.Context) {
i.ctx.Done()
return nil
}

View File

@ -1,2 +1,14 @@
# 资源管理
## CMDB需求介绍
公司所有的资产(ITMS), 运维管理的资产(虚拟机), 为这些资产提供维护(日志清理, 安全检查, 监控告警, ...), Excel
1. 维护麻烦, 静态的, 每次变更都需要人为把信息同步给其他系统。
2. 没法通过API提供数据给其他系统, 没法做系统集成, 系统化程度低。
大的数据库通过为这些数据提供API, 实现数据的 高速集成, 数据一定准确
## 常见的CMDB设计模式

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

View File

@ -0,0 +1,2 @@
# 资源管理模块

View File

@ -0,0 +1 @@
package api

View 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

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

View 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")
}

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

View 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 ""
}

View File

@ -0,0 +1,2 @@
# 资源凭证管理

View File

@ -0,0 +1 @@
package api

View File

@ -0,0 +1,5 @@
package secret
const (
TASK_LABLE_SECRET_ID = "secret_id"
)

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

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

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

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

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

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

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

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

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

View 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="&lt;h1 style=&quot;margin-top: 0px;&quot;&gt;基于模型&lt;/h1&gt;&lt;p&gt;这里的模型,数据库里面的一种表&lt;/p&gt;&lt;p&gt;基于关系性数据库,设计 表概念&lt;/p&gt;&lt;p&gt;1. 传统: 数据库里面添加表&lt;/p&gt;&lt;p&gt;2.&amp;nbsp; 通过cmdb提供的API来添加模型&lt;/p&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;" 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表&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;模型的名称(ec2)&lt;/div&gt;" 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 字段表&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;模型的字段&lt;/div&gt;&lt;div&gt;name: 名称, ip: 主机的IP)&lt;/div&gt;" 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="实例表:&lt;div&gt;核心 记录 模型字段对应的数据&lt;/div&gt;&lt;div&gt;name字段编号(1): ec2_01&lt;/div&gt;&lt;div&gt;ip的字段编号(2): 10.10.10.1&amp;nbsp;&lt;/div&gt;" 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="&lt;div&gt;&lt;br&gt;&lt;/div&gt;ec2实例[] {&lt;div&gt;&lt;font color=&quot;#000000&quot;&gt;name: ec2_01&lt;/font&gt;&lt;/div&gt;&lt;div&gt;&lt;font color=&quot;#000000&quot;&gt;ip: 10.10.10.1&lt;/font&gt;&lt;/div&gt;&lt;div&gt;&lt;font color=&quot;#000000&quot;&gt;&lt;br&gt;&lt;/font&gt;&lt;div&gt;}&lt;/div&gt;&lt;/div&gt;" 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="&lt;h1 style=&quot;margin-top: 0px;&quot;&gt;基于表的CMDB&lt;/h1&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;&lt;p&gt;沿用关系性数据库的设计逻辑, 已有的数据库工具&lt;/p&gt;&lt;p&gt;1. 提供一组API来让用户录入数控并且通过API查询数据库&lt;/p&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;" 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="资源表&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;通用字段,&amp;nbsp; 通过直接添加, 非通用字段, 直接存储为json&lt;/div&gt;" 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="&lt;div&gt;凭证管理&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;存储敏感数据的表(对称加解密)&lt;/div&gt;" 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="&lt;h1 style=&quot;margin-top: 0px;&quot;&gt;敏感数据&lt;/h1&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;background-color: transparent;&quot;&gt;1.&amp;nbsp; 存储加密, 访问运维看到数据库&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;background-color: transparent;&quot;&gt;1.&amp;nbsp; API 提供的数据 需要脱名这种数据是不允许通过API对外开放的, 只允许进程内使用&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;" 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>

View 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"))
}

View File

@ -11,7 +11,7 @@
database = "devcloud_go18"
username = "root"
password = "123456"
auto_migrate = false
auto_migrate = true
debug = false
[mongo]

View File

@ -0,0 +1,3 @@
TX_API_KEY=
TX_API_SECRET=

View File

@ -49,6 +49,11 @@ func (i *PolicyServiceImpl) QueryEndpoint(ctx context.Context, in *policy.QueryE
return nil, err
}
// 没有权限
if policies.Len() == 0 {
return resp, nil
}
if policies.Len() > 1 {
return nil, fmt.Errorf("同一个空间下, 一个用户有多条[%d]授权", policies.Len())
}

View File

@ -11,16 +11,18 @@ import (
func (h *UserRestfulApiHandler) QueryApplication(r *restful.Request, w *restful.Response) {
req := application.NewQueryApplicationRequest()
if err := binding.Query.Bind(r.Request, &req.QueryApplicationRequestSpec); err != nil {
response.Failed(w, err)
return
}
// 过滤条件在认证完成后的上下文中
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)

View File

@ -12,10 +12,10 @@ func TestCreateApplication(t *testing.T) {
req.Description = "应用研发云"
req.Type = application.TYPE_SOURCE_CODE
req.CodeRepository = application.CodeRepository{
SshUrl: "git@122.51.31.227:go-course/go18.git",
SshUrl: "git@122.51.31.227:go-course/go17.git",
}
req.SetNamespaceId(1)
req.SetLabel("team", "dev01.web_developer")
req.SetLabel("team", "dev01.backend_developer")
ins, err := svc.CreateApplication(ctx, req)
if err != nil {
t.Fatal(err)
@ -27,7 +27,7 @@ func TestCreateApplication(t *testing.T) {
func TestQueryApplication(t *testing.T) {
req := application.NewQueryApplicationRequest()
// dev01.%
// req.SetNamespaceId(1)
req.SetNamespaceId(1)
req.SetScope("team", []string{"dev01%"})
// req.SetScope("env", []string{"prod"})
ins, err := svc.QueryApplication(ctx, req)

11
go.mod
View File

@ -8,12 +8,15 @@ require (
github.com/emicklei/go-restful/v3 v3.12.2
github.com/gin-gonic/gin v1.10.0
github.com/google/uuid v1.6.0
github.com/infraboard/mcube/v2 v2.0.61
github.com/infraboard/modules v0.0.12
github.com/infraboard/devops v0.0.6
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/zerolog v1.34.0
github.com/segmentio/kafka-go v0.4.47
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
golang.org/x/crypto v0.38.0
gopkg.in/yaml.v3 v3.0.1
@ -31,7 +34,7 @@ require (
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // 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/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gin-contrib/sse v1.0.0 // indirect
@ -79,6 +82,7 @@ require (
github.com/prometheus/common v0.63.0 // indirect
github.com/prometheus/procfs v0.16.0 // 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/pflag v1.0.6 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
@ -116,5 +120,6 @@ require (
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.5.0 // 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
)

22
go.sum
View File

@ -22,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/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.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.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/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/emicklei/go-restful-openapi/v2 v2.11.0 h1:Ur+yGxoOH/7KRmcj/UoMFqC3VeNc9VOe+/XidumxTvk=
@ -94,10 +95,12 @@ 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/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
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/mcube/v2 v2.0.61/go.mod h1:TbYs8cnD8Cg19sTdU0D+vqWAN+LzoxhMYWmAC2pfJkQ=
github.com/infraboard/modules v0.0.12 h1:vQqm+JwzmhL+hcD9SV+WVlp9ecInc7NsbGahcTmJ0Wk=
github.com/infraboard/modules v0.0.12/go.mod h1:NdgdH/NoeqibJmFPn9th+tisMuR862/crbXeH4FPMaU=
github.com/infraboard/devops v0.0.6 h1:oo7RfRBxu9hbI/+bYzXuX40BfG1hRyVtxC0fBhoFGBU=
github.com/infraboard/devops v0.0.6/go.mod h1:Ac+W3wFy5pG9EH7f1W8DxiAZRhnTDFHrp27WKkYnNoU=
github.com/infraboard/mcube/v2 v2.0.63 h1:OUmDajfLAQ1LUVEutCSRbDIQItyLSPHYWXUJLO6IbaE=
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/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA=
@ -178,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-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
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/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
@ -204,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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
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/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
@ -350,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/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
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/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=

3
skills/html/README.md Normal file
View File

@ -0,0 +1,3 @@
# HTML
vscode 插件: open in browser

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

39
skills/html/index.css Normal file
View 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
View 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>

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

View 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(&lt;div&gt;aysnc executor&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;event pool)&lt;/div&gt;" 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>

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

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

View File

@ -116,7 +116,7 @@ func TestReadMessage(t *testing.T) {
GroupID: "devcloud-go18-audit",
// 可以指定Partition消费消息
// Partition: 0,
Topic: "audit_go18",
Topic: "task_run_events",
MinBytes: 10e3, // 10KB
MaxBytes: 10e6, // 10MB
})