add label

This commit is contained in:
yumaojun03 2025-06-22 23:53:53 +08:00
parent c1f263ab84
commit 13ea1f2e04
13 changed files with 376 additions and 10 deletions

View File

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

View File

@ -0,0 +1,9 @@
# 资源标签管理(数据字典)
+ key:value
Team Label: 用户组
![](./design.drawio)
## 如何基于关系性数据库设计Tree结构

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

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

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

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

View File

@ -0,0 +1,62 @@
package impl
import (
"context"
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/label"
"github.com/infraboard/mcube/v2/ioc/config/datasource"
"github.com/infraboard/mcube/v2/types"
)
// 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
}
// DeleteLabel implements label.Service.
func (i *LabelServiceImpl) DeleteLabel(ctx context.Context, in *label.DeleteLabelRequest) (*label.Label, error) {
panic("unimplemented")
}
// DescribeLabel implements label.Service.
func (i *LabelServiceImpl) DescribeLabel(ctx context.Context, in *label.DescribeLabelRequest) (*label.Label, error) {
panic("unimplemented")
}
// UpdateLabel implements label.Service.
func (i *LabelServiceImpl) UpdateLabel(ctx context.Context, in *label.UpdateLabelRequest) (*label.Label, error) {
panic("unimplemented")
}

View File

@ -0,0 +1,24 @@
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 = "tech"
req.KeyDesc = "技术部"
req.ValueType = label.VALUE_TYPE_ENUM
req.AddEnumOption(&label.EnumOption{
Label: "开发一组",
Value: "dev01",
})
ins, err := svc.CreateLabel(ctx, req)
if err != nil {
t.Fatal(err)
}
t.Log(ins)
}

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

View File

@ -0,0 +1,120 @@
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(),
Spec: spc,
}, nil
}
type Label struct {
// 基础数据
apps.ResourceMeta
// 空间定义
Spec *CreateLabelRequest `json:"spec" 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不允许修改, 带前缀的 tech.dev.frontend01 tech.dev.backend01
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)"`
// 值类型
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,omitempty" 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"`
// 标签的颜色
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)"`
}

View File

@ -20,9 +20,9 @@ func TestQueryPolicy(t *testing.T) {
func TestCreatePolicy(t *testing.T) {
req := policy.NewCreatePolicyRequest()
req.SetNamespaceId(1)
req.UserId = 2
req.RoleId = []uint64{1}
req.SetNamespaceId(1)
set, err := impl.CreatePolicy(ctx, req)
if err != nil {
t.Fatal(err)

View File

@ -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/label/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/role/impl"

View File

@ -40,14 +40,6 @@ func (i *ApplicationServiceImpl) QueryApplication(ctx context.Context, in *appli
query = query.Where("ready = ?", *in.Ready)
}
if in.NamespaceId != nil {
query = query.Where("namespace = ?", in.NamespaceId)
}
// 过滤条件, Label
if in.Scope != nil {
}
err := query.Count(&set.Total).Error
if err != nil {
return nil, err