Browse Source

Merge branch 'master' of https://git.openi.org.cn/openioctopus/octopus

pull/207/head
Lijunmao 4 days ago
parent
commit
7bd4b41abc
28 changed files with 127 additions and 171 deletions
  1. +1
    -1
      .golangci.yaml
  2. +5
    -2
      admin-portal/src/api/globalVariable.js
  3. +1
    -1
      admin-portal/src/views/clusterMonitor/clusterMonitor.vue
  4. +2
    -9
      admin-portal/src/views/devManager/components/notebook/notebookInfo.vue
  5. +1
    -1
      admin-portal/src/views/platformManager/components/platformConfig.vue
  6. +1
    -1
      admin-portal/src/views/platformManager/index.vue
  7. +10
    -8
      admin-portal/src/views/platformManager/platformTrainingTaskList.vue
  8. +6
    -3
      admin-portal/src/views/userManager/components/userConfig.vue
  9. +0
    -11
      deploy/charts/octopus/templates/base-server.yaml
  10. +11
    -2
      openai-portal/src/styles/dot.scss
  11. +3
    -9
      openai-portal/src/views/modelDev/components/notebook/notebookInfo.vue
  12. +2
    -10
      server/admin-server/api/v1/platform.proto
  13. +2
    -10
      server/admin-server/api/v1/user.proto
  14. +2
    -10
      server/base-server/api/v1/platform.proto
  15. +2
    -11
      server/base-server/api/v1/user.proto
  16. +0
    -20
      server/base-server/configs/config.yaml
  17. +1
    -4
      server/base-server/internal/common/constant.go
  18. +13
    -0
      server/base-server/internal/common/platform_config_key.go
  19. +13
    -0
      server/base-server/internal/common/user_config_key.go
  20. +0
    -20
      server/base-server/internal/conf/conf.proto
  21. +8
    -1
      server/base-server/internal/data/dao/platform/platform.go
  22. +8
    -1
      server/base-server/internal/data/dao/user.go
  23. +2
    -2
      server/base-server/internal/data/data.go
  24. +3
    -11
      server/base-server/internal/service/platform/platform.go
  25. +4
    -5
      server/base-server/internal/service/platform/train_job.go
  26. +2
    -13
      server/base-server/internal/service/user/user.go
  27. +9
    -5
      server/common/api/v1/configkey.go
  28. +15
    -0
      server/common/api/v1/configkey.proto

+ 1
- 1
.golangci.yaml View File

@@ -73,4 +73,4 @@ run:
- node.go
- develop.go
- train_job.go
- conf.go
- api/v1/configkey.go

+ 5
- 2
admin-portal/src/api/globalVariable.js View File

@@ -4,8 +4,11 @@ if (process.env.NODE_ENV === 'development') {
// eslint-disable-next-line no-undef
DOMAIN = process.env.VUE_APP_BASE_DOMAIN || 'http://192.168.202.73'
} else {
if (!window.location.port || window.location.port == '') { DOMAIN = window.location.protocol + '//' + document.domain }
else { DOMAIN = window.location.protocol + '//' + document.domain + ':' + window.location.port }
if (!window.location.port || window.location.port == '') {
DOMAIN = window.location.protocol + '//' + document.domain
} else {
DOMAIN = window.location.protocol + '//' + document.domain + ':' + window.location.port
}
}
export default {
DOMAIN


+ 1
- 1
admin-portal/src/views/clusterMonitor/clusterMonitor.vue View File

@@ -1,7 +1,7 @@
<template>
<div>
<iframe
src="http://192.168.203.156/grafana/d/ft1oaQnWk/clustermetrics?orgId=1&refresh=10s&from=now-5m&to=now&var-Node=All"
src="http://192.168.202.73/grafana/d/ft1oaQnWk/clustermetrics?orgId=1&refresh=10s&from=now-5m&to=now&var-Node=All"
:height="iFrameHeight"
>
</iframe>


+ 2
- 9
admin-portal/src/views/devManager/components/notebook/notebookInfo.vue View File

@@ -9,8 +9,7 @@
<div>是否分布式:<span>否</span></div>
</el-col>
</el-row>
<el-input
v-if="showInfo"
<el-input
v-model="subTaskInfo"
type="textarea"
:readonly="true"
@@ -20,7 +19,6 @@

<div class="block">
<el-pagination
v-if="showInfo"
:current-page="pageIndex"
:page-sizes="[10, 20, 50, 80]"
:page-size="pageSize"
@@ -48,7 +46,6 @@ export default {
data() {
return {
notebookInfo: {},
showInfo: false,
infoVisible: true,
subTaskInfo: "",
total: 0,
@@ -73,14 +70,10 @@ export default {
}
getNotebookInfo(param).then(response => {
if (!response.success) {
this.$message({
message: "暂无相关运行信息",
type: 'warning'
});
this.subTaskInfo = "暂无相关运行信息"
return
}

this.showInfo = response.payload.notebookEvents.length
this.total = response.payload.totalSize

let infoMessage = ""


+ 1
- 1
admin-portal/src/views/platformManager/components/platformConfig.vue View File

@@ -14,7 +14,7 @@
<el-form-item>
<div v-for="(item, index) in ruleForm.platformConfig" :key="index">
<el-form-item>
<strong>{{item.key+": "}}</strong>
<strong>{{item.title+": "}}</strong>
<el-popover
placement="top"
width="400"


+ 1
- 1
admin-portal/src/views/platformManager/index.vue View File

@@ -4,7 +4,7 @@
<el-tab-pane label="平台列表" name="platformList">
<platformList></platformList>
</el-tab-pane>
<el-tab-pane label="平台训练任务列表" name="trainingTask">
<el-tab-pane label="训练任务列表" name="trainingTask">
<platformTrainingTaskList></platformTrainingTaskList>
</el-tab-pane>
</el-tabs>


+ 10
- 8
admin-portal/src/views/platformManager/platformTrainingTaskList.vue View File

@@ -11,14 +11,9 @@
<span>{{ scope.row.name }}</span>
</template>
</el-table-column>
<el-table-column label="数据集" align="center">
<el-table-column label="镜像" align="center">
<template slot-scope="scope">
<span>{{ scope.row.datasets }}</span>
</template>
</el-table-column>
<el-table-column label="联系方式" align="center">
<template slot-scope="scope">
<span>{{ scope.row.contactInfo }}</span>
<span>{{ scope.row.image.name+":"+scope.row.image.version }}</span>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center">
@@ -59,6 +54,14 @@ export default {
searchData: {
pageIndex: 1,
pageSize: 10,
},
statusOption: {
'preparing': ['status-ready', '初始中'],
'pending': ['status-agent', '等待中'],
'running': ['status-running', '运行中'],
'failed': ['status-danger', '失败'],
'succeeded': ['status-success', '成功'],
'stopped': ['status-stopping', '已停止']
}
}
},
@@ -71,7 +74,6 @@ export default {
},
getPlatformTrainingTaskList(param){
getPlatformTrainingTaskList(param).then(response => {
console.log("res:",response.data.trainJobs)
if(response.success){
this.taskList = response.data.trainJobs;
this.total = response.data.totalSize


+ 6
- 3
admin-portal/src/views/userManager/components/userConfig.vue View File

@@ -15,7 +15,7 @@
<el-form-item>
<div v-for="(item, index) in ruleForm.userConfig" :key="index">
<el-form-item>
<strong>{{item.key+": "}}</strong>
<strong>{{item.title+": "}}</strong>
<el-popover
placement="top"
width="400"
@@ -27,8 +27,11 @@
</el-popover>
<el-input v-if="item.type === 'input'" v-model="item.value" style="width: 40%;"></el-input>
<el-radio-group v-if="item.type === 'radio'" v-model="item.value">
<el-radio :label="'yes'"></el-radio>
<el-radio :label="'no'"></el-radio>
<span v-for="(option, index) in item.options" :key="index">
<el-radio :label="option" style="margin-right:15px"></el-radio>
</span>
<!-- <el-radio :label="'yes'"></el-radio>
<el-radio :label="'no'"></el-radio> -->
</el-radio-group>
</el-form-item>
</div>


+ 0
- 11
deploy/charts/octopus/templates/base-server.yaml View File

@@ -164,17 +164,6 @@ data:
discoveryLeaderLeaseLockName: resourcediscovery
discoveryDuration: 15s
ignoreSystemResources: hugepages-1Gi,pods,hugepages-2Mi,ephemeral-storage
platform:
configKeys:
- key: jobStatusCallbackAddr
title: 任务状态回调地址
type: input
user:
configKeys:
- key: jointCloudPermission
title: 云际权限
type: radio
options: yes,no
administrator:
username: {{ .Values.baseserver.administrator.username }}
password: "{{ .Values.baseserver.administrator.password }}"


+ 11
- 2
openai-portal/src/styles/dot.scss View File

@@ -40,8 +40,17 @@
width: 7px;
height:7px;
background:#e6a23c;
border-radius:50%;
margin-right: 5px;
border-radius:50%;
margin-right: 5px;
}
// 重分派
.status-reassign{
display:inline-block;
width: 7px;
height:7px;
background:#006400;
border-radius:50%;
margin-right: 5px;
}
// 失败
.status-danger{


+ 3
- 9
openai-portal/src/views/modelDev/components/notebook/notebookInfo.vue View File

@@ -10,7 +10,7 @@
</el-col>
</el-row>
<el-input
v-if="showInfo"
v-model="subTaskInfo"
type="textarea"
:readonly="true"
@@ -19,8 +19,7 @@
</div>

<div class="block">
<el-pagination
v-if="showInfo"
<el-pagination
:current-page="pageIndex"
:page-sizes="[10, 20, 50, 80]"
:page-size="pageSize"
@@ -48,7 +47,6 @@ export default {
data() {
return {
notebookInfo: {},
showInfo: false,
infoVisible: true,
subTaskInfo: "",
total: 0,
@@ -73,14 +71,10 @@ export default {
}
getNotebookInfo(param).then(response => {
if (!response.success) {
this.$message({
message: "暂无相关运行信息",
type: 'warning'
});
this.subTaskInfo = "暂无相关运行信息"
return
}

this.showInfo = response.payload.notebookEvents.length
this.total = response.payload.totalSize

let infoMessage = ""


+ 2
- 10
server/admin-server/api/v1/platform.proto View File

@@ -7,6 +7,7 @@ option go_package = "server/admin-server/api/v1;v1";

import "google/api/annotations.proto";
import "validate/validate.proto";
import "server/common/api/v1/configkey.proto";

service PlatformService {
// 查询第三方平台列表
@@ -190,16 +191,7 @@ message ListPlatformConfigKeyRequest {
}

message ListPlatformConfigKeyReply {
message ConfigKey {
string key = 1; // 键值,必填,保存配置时传入
string title = 2; // 标题,必填
string type = 3; // 类型,必填,input、radio、checkbox,默认为input
string desc = 4; // 描述,选填
string options = 5; // 选项,选填,radio、checkbox时必填,逗号分隔
string regexp = 6; // 正则,选填
bool required = 7; // 选填,默认为false
}
repeated ConfigKey configKeys = 1;
repeated common.api.v1.ConfigKey configKeys = 1;
}

message GetPlatformConfigRequest {


+ 2
- 10
server/admin-server/api/v1/user.proto View File

@@ -9,6 +9,7 @@ option go_package = "server/admin-server/api/v1;v1";
import "google/api/annotations.proto";
import "validate/validate.proto";
import "server/admin-server/api/v1/workspace.proto";
import "server/common/api/v1/configkey.proto";

service User {
rpc ListUser (ListUserRequest) returns (ListUserReply) {
@@ -150,16 +151,7 @@ message ListUserConfigKeyRequest {
}

message ListUserConfigKeyReply {
message ConfigKey {
string key = 1; // 键值,必填,保存配置时传入
string title = 2; // 标题,必填
string type = 3; // 类型,必填,input、radio、checkbox,默认为input
string desc = 4; // 描述,选填
string options = 5; // 选项,选填,radio、checkbox时必填,逗号分隔
string regexp = 6; // 正则,选填
bool required = 7; // 选填,默认为false
}
repeated ConfigKey configKeys = 1;
repeated common.api.v1.ConfigKey configKeys = 1;
}

message GetUserConfigRequest {


+ 2
- 10
server/base-server/api/v1/platform.proto View File

@@ -7,6 +7,7 @@ option go_package = "server/base-server/api/v1;v1";


import "validate/validate.proto";
import "server/common/api/v1/configkey.proto";

service PlatformService {
rpc ListPlatform (ListPlatformRequest) returns (ListPlatformReply);
@@ -85,16 +86,7 @@ message ListPlatformConfigKeyRequest {
}

message ListPlatformConfigKeyReply {
message ConfigKey {
string key = 1; // 键值,必填,保存配置时传入
string title = 2; // 标题,必填
string type = 3; // 类型,选填,input、radio、checkbox,默认为input
string desc = 4; // 描述,选填
string options = 5; // 选项,选填,radio、checkbox时必填,逗号分隔
string regexp = 6; // 正则,选填
bool required = 7; // 选填,默认为false
}
repeated ConfigKey configKeys = 1;
repeated common.api.v1.ConfigKey configKeys = 1;
}

message StorageOptions {


+ 2
- 11
server/base-server/api/v1/user.proto View File

@@ -5,7 +5,7 @@ package baseserver.api.v1;
option go_package = "server/base-server/api/v1;v1";


import "server/common/api/v1/configkey.proto";
import "validate/validate.proto";

service UserService {
@@ -112,16 +112,7 @@ message ListUserConfigKeyRequest {
}

message ListUserConfigKeyReply {
message ConfigKey {
string key = 1; // 键值,必填,保存配置时传入
string title = 2; // 标题,必填
string type = 3; // 类型,必填,input、radio、checkbox,默认为input
string desc = 4; // 描述,选填
string options = 5; // 选项,选填,radio、checkbox时必填,逗号分隔
string regexp = 6; // 正则,选填
bool required = 7; // 选填,默认为false
}
repeated ConfigKey configKeys = 1;
repeated common.api.v1.ConfigKey configKeys = 1;
}

message GetUserConfigRequest {


+ 0
- 20
server/base-server/configs/config.yaml View File

@@ -74,26 +74,6 @@ service:
discoveryLeaderLeaseLockName: resourcediscovery
discoveryDuration: 15s
ignoreSystemResources: hugepages-1Gi,pods,hugepages-2Mi,ephemeral-storage
platform:
# 数组内字段为
# key:键值,必填,保存配置时传入
# title:标题,必填
# type:类型,必填,input、radio、checkbox
# desc:描述,选填
# options:选项,选填,radio、checkbox时必填,逗号分隔
# regexp:正则,选填
# required: 选填,默认为false
configKeys:
- key: jobStatusCallbackAddr
title: 任务状态回调地址
type: input
user:
# 参考平台配置
configKeys:
- key: jointCloudPermission
title: 云际权限
type: radio
options: yes,no
administrator:
username: "admin"
password: "123456"


+ 1
- 4
server/base-server/internal/common/constant.go View File

@@ -5,8 +5,5 @@ const (
)

const (
StorageTypeJuicefs string = "juicefs"
ConfigKeyTypeInput string = "input"
ConfigKeyTypeRadio string = "radio"
ConfigKeyTypeCheckBox string = "checkbox"
StorageTypeJuicefs string = "juicefs"
)

+ 13
- 0
server/base-server/internal/common/platform_config_key.go View File

@@ -0,0 +1,13 @@
package common

import v1 "server/common/api/v1"

const (
PlatformKeyJobStatusCallbackAddr = "jobStatusCallbackAddr"
)

var (
PlatformConfigKeys = []*v1.ConfigKey{
{Key: PlatformKeyJobStatusCallbackAddr, Title: "任务状态回调地址", Type: v1.ConfigKeyTypeInput},
}
)

+ 13
- 0
server/base-server/internal/common/user_config_key.go View File

@@ -0,0 +1,13 @@
package common

import v1 "server/common/api/v1"

const (
UserKeyJointCloudPermission = "jointCloudPermission"
)

var (
UserConfigKeys = []*v1.ConfigKey{
{Key: UserKeyJointCloudPermission, Title: "云际互联权限", Type: v1.ConfigKeyTypeRadio, Options: []string{"yes", "no"}},
}
)

+ 0
- 20
server/base-server/internal/conf/conf.proto View File

@@ -117,24 +117,6 @@ message Resource {
string ignoreSystemResources = 11;
}

message ConfigKey {
string key = 1; // 键值,必填,保存配置时传入
string title = 2; // 标题,必填
string desc = 3; // 描述,选填
string type = 4; // 类型,选填,input、radio、checkbox,默认为input radio
string options = 5; // 选项,选填,radio、checkbox时必填,逗号分隔 yes,no
string regexp = 6; // 正则,选填
bool required = 7; // 选填,默认为false
}

message Platform {
repeated ConfigKey configKeys = 1;
}

message User {
repeated ConfigKey configKeys = 1;
}

message JointCloud {
string baseUrl = 1;
string username = 2;
@@ -151,8 +133,6 @@ message Service {
Resource resource = 7;
string resourceLabelKey = 8;
int64 billingPeriodSec = 9;
Platform platform = 10;
User user = 11;
}

message Administrator {


+ 8
- 1
server/base-server/internal/data/dao/platform/platform.go View File

@@ -223,7 +223,14 @@ func (d *platformDao) UpdatePlatformConfig(ctx context.Context, platformId strin
return errors.Errorf(nil, errors.ErrorInvalidRequestParameter)
}

c := &model.PlatformConfig{PlatformId: platformId, Config: config}
configUp := make(map[string]string)
for k, v := range config {
if v != "" {
configUp[k] = v
}
}

c := &model.PlatformConfig{PlatformId: platformId, Config: configUp}
res := db.Clauses(clause.OnConflict{
UpdateAll: true,
}).Create(c)


+ 8
- 1
server/base-server/internal/data/dao/user.go View File

@@ -152,7 +152,14 @@ func (d *userDao) UpdateConfig(ctx context.Context, userId string, config map[st
return commerrors.Errorf(nil, commerrors.ErrorInvalidRequestParameter)
}

c := &model.UserConfig{UserId: userId, Config: config}
configUp := make(map[string]string)
for k, v := range config {
if v != "" {
configUp[k] = v
}
}

c := &model.UserConfig{UserId: userId, Config: configUp}
res := db.Clauses(clause.OnConflict{
UpdateAll: true,
}).Create(c)


+ 2
- 2
server/base-server/internal/data/data.go View File

@@ -58,7 +58,7 @@ func NewData(confData *conf.Data, logger log.Logger) (*Data, func(), error) {
return nil, nil, err
}

influxdb, _ := influxdb.NewInfluxdb(confData)
influxdb, err := influxdb.NewInfluxdb(confData)
if err != nil {
return nil, nil, err
}
@@ -243,7 +243,7 @@ func dbInit(confData *conf.Data) (*gorm.DB, error) {
if err != nil {
return nil, err
}
err = db.AutoMigrate(&platformModel.Platform{})
if err != nil {
return nil, err


+ 3
- 11
server/base-server/internal/service/platform/platform.go View File

@@ -3,6 +3,7 @@ package platform
import (
"context"
api "server/base-server/api/v1"
"server/base-server/internal/common"
"server/base-server/internal/conf"
"server/base-server/internal/data"
model "server/base-server/internal/data/dao/model/platform"
@@ -121,16 +122,7 @@ func (s *platformService) UpdatePlatform(ctx context.Context, req *api.UpdatePla
}

func (s *platformService) ListPlatformConfigKey(ctx context.Context, req *api.ListPlatformConfigKeyRequest) (*api.ListPlatformConfigKeyReply, error) {
reply := &api.ListPlatformConfigKeyReply{}
for _, i := range s.conf.Service.Platform.ConfigKeys {
k := &api.ListPlatformConfigKeyReply_ConfigKey{}
err := copier.Copy(k, i)
if err != nil {
return nil, err
}
reply.ConfigKeys = append(reply.ConfigKeys, k)
}
return reply, nil
return &api.ListPlatformConfigKeyReply{ConfigKeys: common.PlatformConfigKeys}, nil
}

func (s *platformService) ListPlatformStorageConfig(ctx context.Context, req *api.ListPlatformStorageConfigRequest) (*api.ListPlatformStorageConfigReply, error) {
@@ -231,7 +223,7 @@ func (s *platformService) GetPlatformConfig(ctx context.Context, req *api.GetPla
func (s *platformService) UpdatePlatformConfig(ctx context.Context, req *api.UpdatePlatformConfigRequest) (*api.UpdatePlatformConfigReply, error) {
for k, v := range req.Config {
in := false
for _, i := range s.conf.Service.Platform.ConfigKeys {
for _, i := range common.PlatformConfigKeys {
if k == i.Key {
in = true
err := i.ValidateValue(v)


+ 4
- 5
server/base-server/internal/service/platform/train_job.go View File

@@ -28,10 +28,9 @@ import (
)

const (
k8sTaskNamePrefix = "task"
NoDistributedJobNum = 1
shmResource = "shm"
jobStatusCallbackAddr = "jobStatusCallbackAddr"
k8sTaskNamePrefix = "task"
NoDistributedJobNum = 1
shmResource = "shm"
)

type platformTrainJobService struct {
@@ -1032,7 +1031,7 @@ func (s *platformTrainJobService) updatePlatfromJobStatus(ctx context.Context, p
if err != nil {
return err
}
if url, ok := reply.Config[jobStatusCallbackAddr]; ok {
if url, ok := reply.Config[common.PlatformKeyJobStatusCallbackAddr]; ok {
platformReply, err := s.platformService.BatchGetPlatform(ctx, &api.BatchGetPlatformRequest{Ids: []string{platformId}})
if err != nil {
return err


+ 2
- 13
server/base-server/internal/service/user/user.go View File

@@ -10,8 +10,6 @@ import (
"server/common/errors"
"server/common/utils"

"github.com/jinzhu/copier"

"server/common/log"

"golang.org/x/crypto/bcrypt"
@@ -263,16 +261,7 @@ func (s *UserService) ListUserInCond(ctx context.Context, req *api.ListUserInCon
}

func (s *UserService) ListUserConfigKey(ctx context.Context, req *api.ListUserConfigKeyRequest) (*api.ListUserConfigKeyReply, error) {
reply := &api.ListUserConfigKeyReply{}
for _, i := range s.conf.Service.User.ConfigKeys {
k := &api.ListUserConfigKeyReply_ConfigKey{}
err := copier.Copy(k, i)
if err != nil {
return nil, err
}
reply.ConfigKeys = append(reply.ConfigKeys, k)
}
return reply, nil
return &api.ListUserConfigKeyReply{ConfigKeys: common.UserConfigKeys}, nil
}

func (s *UserService) GetUserConfig(ctx context.Context, req *api.GetUserConfigRequest) (*api.GetUserConfigReply, error) {
@@ -289,7 +278,7 @@ func (s *UserService) GetUserConfig(ctx context.Context, req *api.GetUserConfigR
func (s *UserService) UpdateUserConfig(ctx context.Context, req *api.UpdateUserConfigRequest) (*api.UpdateUserConfigReply, error) {
for k, v := range req.Config {
in := false
for _, i := range s.conf.Service.User.ConfigKeys {
for _, i := range common.UserConfigKeys {
if k == i.Key {
in = true
err := i.ValidateValue(v)


server/base-server/internal/conf/conf.go → server/common/api/v1/configkey.go View File

@@ -1,11 +1,15 @@
package conf
package v1

import (
"regexp"
"server/base-server/internal/common"
"server/common/errors"
"server/common/utils"
"strings"
)

const (
ConfigKeyTypeInput = "input"
ConfigKeyTypeRadio = "radio"
ConfigKeyTypeCheckBox = "checkbox"
)

func (k *ConfigKey) ValidateValue(v string) error {
@@ -13,8 +17,8 @@ func (k *ConfigKey) ValidateValue(v string) error {
return errors.Errorf(nil, errors.ErrorConfigValueValidateFailed)
}

if (k.Type == common.ConfigKeyTypeRadio || k.Type == common.ConfigKeyTypeCheckBox) && v != "" &&
!utils.StringSliceContainsValue(strings.Split(k.Options, ","), v) {
if (k.Type == ConfigKeyTypeRadio || k.Type == ConfigKeyTypeCheckBox) && v != "" &&
!utils.StringSliceContainsValue(k.Options, v) {
return errors.Errorf(nil, errors.ErrorConfigValueValidateFailed)
}


+ 15
- 0
server/common/api/v1/configkey.proto View File

@@ -0,0 +1,15 @@
syntax = "proto3";

package common.api.v1;

option go_package = "server/common/api/v1;v1";

message ConfigKey {
string key = 1; // 键值,必填,保存配置时传入
string title = 2; // 标题,必填
string type = 3; // 类型,选填,input、radio、checkbox,默认为input
string desc = 4; // 描述,选填
repeated string options = 5; // 选项,选填,radio、checkbox时必填
string regexp = 6; // 正则,选填
bool required = 7; // 选填,默认为false
}

Loading…
Cancel
Save