【k8s多集群管理平台开发实践】八、client-go实现service读取列表、创建service、读取yaml配置并更新

news2024/11/26 12:13:22

文章目录

        • 简介
      • 一.k8s的service列表
        • 1.1.controllers控制器代码
        • 1.2.models模型代码
      • 二.创建service
        • 2.1.controllers控制器代码
        • 2.2.models模分代码
      • 三.读取和更新service的yaml配置
        • 3.1.controllers控制器代码
        • 3.2.models模型代码
      • 四.路由设置
        • 4.1.路由设置
      • 五.前端代码
        • 5.1.列表部分html代码
        • 5.2.创建表单html代码
        • 5.3.显示yaml配置的html代码
      • 六.完整代码
        • 6.1.控制器service.go的完整代码
        • 6.2.模型serviceModel.go的完整代码
      • 七.效果图

简介

本章节主要讲解通过client-go实现service的列表读取和界面创建service,sevice的yaml配置文件读取和修改,并通过layui实现界面操作,其中包含控制器这部分的代码,模型这部分代码,以及前端的html代码。

一.k8s的service列表

1.1.controllers控制器代码

通过传递集群id、命名空间、服务名称并调用模型代码中serviceList函数来读取列表

func (this *SvcController) List() {
	clusterId := this.GetString("clusterId")    //集群名称
	nameSpace := this.GetString("nameSpace")    //命名空间
	serviceName := this.GetString("serviceName")    //服务名称
	labels := this.GetString("labels")  //标签:labels=key:value
	labelsKV := strings.Split(labels, ":")
	var labelsKey, labelsValue string
	if len(labelsKV) == 2 {
		labelsKey = labelsKV[0]
		labelsValue = labelsKV[1]
	}
	//根据传递的参数进行过滤返回服务列表
	svcList, err := m.SvcList(clusterId, nameSpace, serviceName, labelsKey, labelsValue)
	msg := "success"
	code := 0
	count := len(svcList)
	if err != nil {
		log.Println(err)
		msg = err.Error()
		code = -1
	}
	this.Data["json"] = &map[string]interface{}{"code": code, "msg": msg, "count": count, "data": &svcList}
	this.ServeJSON()
}

1.2.models模型代码

先定义一个结构体Service,通过传递过来的参数,在svcList.Items中循环读取service的信息,然后赋值结构体Service,并追加到定义一个结构体数组var bbb = make([]Service, 0)

type Service struct {
	ServiceName string `json:"serviceName"` //服务名称
	NameSpace   string `json:"nameSpace"`   //命名空间
	Labels      string `json:"labels"`  //标签
	SvcType     string `json:"svcType"` //service的类型
	SvcIp       string `json:"svcIp"`   //服务IP
	SvcPort     string `json:"svcPort"`
	LanEndpoint string `json:"lanEndpoint"` //内部端点
	WanEndpoint string `json:"wanEndpoint"` //外部端点
	CreateTime  string `json:"createTime"`
}

type ServicePort struct {
	PortName   string `json:"portName"`
	SvcPort    int32  `json:"svcPort"`
	TargetPort int32  `json:"targetPort"`
}

func SvcList(kubeconfig, namespace, serviceName string, labelsKey, labelsValue string) ([]Service, error) {
    //获取链接
	clientset := common.ClientSet(kubeconfig)
	if namespace == "" {    //判断namespace是否为空,空则读取全部命名空间
		namespace = corev1.NamespaceAll
	}
    //定义标签过滤条件
	var listOptions = metav1.ListOptions{}
	if labelsKey != "" && labelsValue != "" {
		listOptions = metav1.ListOptions{LabelSelector: fmt.Sprintf("%s=%s", labelsKey, labelsValue)}
	}
    //读取service信息
	svcList, err := clientset.CoreV1().Services(namespace).List(context.TODO(), listOptions)
	if err != nil {
		log.Printf("list service error:%v\n", err)
	}

	//fmt.Println("svc count:", len(svcList.Items))
	//循环items并追加到结构体数组bbb上
	var bbb = make([]Service, 0)
	for _, svc := range svcList.Items {
		//搜索
		if serviceName != "" {
			if !strings.Contains(svc.Name, serviceName) {
				continue
			}
		}
		//提取标签
		var labelsStr string
		for kk, vv := range svc.ObjectMeta.Labels {
			labelsStr += fmt.Sprintf("%s:%s,", kk, vv)
		}
		if len(labelsStr) > 0 {
			labelsStr = labelsStr[0 : len(labelsStr)-1]
		}
		//提取所有映射的端口
		var wanEndPoint string
		svcType := fmt.Sprintf("%s", svc.Spec.Type)
		if svcType == "LoadBalancer" {
			wanEndPoint = fmt.Sprintf("%s:%d", svc.Status.LoadBalancer.Ingress[0].IP, svc.Spec.Ports[0].Port)
		}
		lanEndPoint := ""
		var svcPort string
		if len(svc.Spec.Ports) > 0 {
			vsapp := svc.Spec.Selector["app"]
			if vsapp == "" {
				vsapp = svc.Spec.Selector["k8s-app"]
			}
			for _, vv := range svc.Spec.Ports {
				//lanEndPoint += fmt.Sprintf("%s:%s:%s\n", svc.Spec.Selector["app"], svc.Spec.Ports[0].TargetPort.StrVal, svc.Spec.Ports[0].Protocol)
				//svcPort += fmt.Sprintf("%s:", svc.Spec.Ports[0].Port)
				var vport string
				if vv.TargetPort.Type == 0 {
					vport = fmt.Sprintf("%d", vv.TargetPort.IntVal)
				} else {
					vport = fmt.Sprintf("%s", vv.TargetPort.StrVal)
				}
				lanEndPoint += fmt.Sprintf("%s:%s:%s,", vsapp, vport, vv.Protocol)
				svcPort += fmt.Sprintf("%d,", vv.Port)
			}
			if len(svcPort) > 0 {
				svcPort = svcPort[0 : len(svcPort)-1]
			}
			lanEndPoint = lanEndPoint[0 : len(lanEndPoint)-1]
		}
		//赋值到结构体
		Items := &Service{
			ServiceName: svc.Name,
			NameSpace:   svc.Namespace,
			SvcType:     svcType,
			SvcIp:       svc.Spec.ClusterIP,
			Labels:      labelsStr,
			SvcPort:     svcPort,
			LanEndpoint: lanEndPoint,
			WanEndpoint: wanEndPoint,
			CreateTime:  svc.CreationTimestamp.Format("2006-01-02 15:04:05"),
		}
		//追加到数组bbb
		bbb = append(bbb, *Items)
	}
	return bbb, err
}

二.创建service

2.1.controllers控制器代码

将前端的form 表单提交的json数据传递到模型函数serviceCreate来进行解析

func (this *SvcController) Create() {
	clusterId := this.GetString("clusterId") //集群ID
	code := 0
	msg := "success"
	//log.Println(string(this.Ctx.Input.RequestBody))
	//将传递过来的json 二进制 body直接传递到模型中的函数SvcCreate处理
	err := m.SvcCreate(clusterId, this.Ctx.Input.RequestBody)
	if err != nil {
		code = -1
		msg = err.Error()
		log.Printf("[ERROR] configmap Create Fail:%s\n", err)
	}
	this.Data["json"] = &map[string]interface{}{"code": code, "msg": msg}
	this.ServeJSON()
}

2.2.models模分代码

将控制器Create函数中传递过来的body数据进行json解析,创建一个service实例结构体svcInstance := &corev1.Service,并将解析json出来的数据赋值到结构体中,再调用clientset.CoreV1().Services(nameSpace).Create来进行创建.

func SvcCreate(kubeconfig string, bodys []byte) error {
    //解析json并提取数据
	gp := gjson.ParseBytes(bodys)
	clusterId := gp.Get("clusterId").String()
	if kubeconfig == "" {
		kubeconfig = clusterId
	}
	serviceName := gp.Get("serviceName").String()   //服务名称
	nameSpace := gp.Get("nameSpace").String()   //命名空间
	svcType := gp.Get("svcType").String()   //服务类型
	deployName := gp.Get("deployName").String() //关联的负载名称
	isHeadless := gp.Get("isHeadless").Str  //是否是无头服务

	var labelsMap = make(map[string]string) //提取标签
	labelsMap["app"] = serviceName
	for _, vv := range gp.Get("lables").Array() {
		labelsMap[vv.Get("key").Str] = vv.Get("value").Str
	}

	selectApp := serviceName    //默认将service的名称和deployment的名称一致,deployName不为空时,按照deployName的名称创建
	if deployName != "" {
		selectApp = deployName
	}

	var serviceType corev1.ServiceType  //赋值服务类型
	switch svcType {
	case "NodePort":
		serviceType = corev1.ServiceTypeNodePort
	case "LoadBalancer":
		serviceType = corev1.ServiceTypeLoadBalancer
	default:
		serviceType = corev1.ServiceTypeClusterIP
	}

	if isHeadless == "on" { //判断是否是无头服务
		serviceType = corev1.ServiceTypeClusterIP
	}

    //将json解析的数据赋值到service实例结构体上
	svcInstance := &corev1.Service{
		ObjectMeta: metav1.ObjectMeta{
			Name:      serviceName,
			Namespace: nameSpace,
			Labels:    labelsMap,
		},
		Spec: corev1.ServiceSpec{
			Selector: map[string]string{
				"app": selectApp,
			},
			Type: serviceType,
		},
	}
	if isHeadless == "on" {
		svcInstance.Spec.ClusterIP = corev1.ClusterIPNone
	}

	ports := gp.Get("ports").Array()
	var svcPorts = make([]corev1.ServicePort, 0)
	for _, vv := range ports {
		var svcProtocol corev1.Protocol
		if vv.Get("protocol").Str == "UDP" {
			svcProtocol = corev1.ProtocolUDP
		} else {
			svcProtocol = corev1.ProtocolTCP
		}
		svcPort := &corev1.ServicePort{
			Name:       vv.Get("portName").Str,
			Port:       int32(vv.Get("svcPort").Int()),
			Protocol:   svcProtocol,
			TargetPort: intstr.FromInt32(int32(vv.Get("targetPort").Int())),
		}
		svcPorts = append(svcPorts, *svcPort)
	}
	svcInstance.Spec.Ports = svcPorts
    
    //根据传递的集群ID进行创建service
	clientset := common.ClientSet(kubeconfig)
	_, err := clientset.CoreV1().Services(nameSpace).Create(context.TODO(), svcInstance, metav1.CreateOptions{})
	if err != nil {
		return err
	}
	return nil
}

三.读取和更新service的yaml配置

3.1.controllers控制器代码

通过传递集群id、命名空间、服务名称来进行读取service,并调用模型代码中GetSvcYaml函数来读取yaml配置

//读取yaml配置
func (this *SvcController) Yaml() {
	clusterId := this.GetString("clusterId")
	nameSpace := this.GetString("nameSpace")
	serviceName := this.GetString("serviceName")
	yamlStr, _ := m.GetSvcYaml(clusterId, nameSpace, serviceName)
	this.Ctx.WriteString(yamlStr)
}
//更新yaml配置
func (this *SvcController) ModifyByYaml() {
	clusterId := this.GetString("clusterId")
	//nameSpace := gp.Get("nameSpace").String()
	//log.Println(string(this.Ctx.Input.RequestBody))
	//提取body并替换掉%,否则解析出错
	bodyByte := []byte(strings.ReplaceAll(string(this.Ctx.Input.RequestBody), "%25", "%"))
	err := m.SvcYamlModify(clusterId, bodyByte)
	if err != nil {
		log.Println(err)
	}
	this.Data["json"] = &map[string]interface{}{"code": 0, "msg": "ok"}
	this.ServeJSON()
}


3.2.models模型代码

先读取service实例信息,然后将实例信息解析成解构,然后解构的字节数据进行yaml序列化处理,并转成字符串进行返回

//读取yaml配置
func GetSvcYaml(kubeconfig, namespace, serviceName string) (string, error) {
    //先通过传递的信息读取service实例
	servicesClient := common.ClientSet(kubeconfig).CoreV1().Services(namespace)
	service, err := servicesClient.Get(context.TODO(), serviceName, metav1.GetOptions{})
	//将读取的service进行解构
	serviceUnstructured, err := runtime.DefaultUnstructuredConverter.ToUnstructured(service)
	if err != nil {
		return "", err
	}
	//将结构的service数据进行序列号成yaml的bytes
	yamlBytes, err := yaml.Marshal(serviceUnstructured)
	if err != nil {
		return "", err
	}
	//以字符串的形式返回
	return string(yamlBytes), nil
}

//更新yaml配置
func SvcYamlModify(kubeconfig string, yamlData []byte) error {
    //转成json格式
	data, err := yamlutil.ToJSON(yamlData)
	if err != nil {
		return err
	}
	//反序列化到service结构体实例
	service := &corev1.Service{}
	err = json.Unmarshal(data, service)
	if err != nil {
		return err
	}
    
    //提取出名称和命名空间
	namespace := service.ObjectMeta.Namespace
	serviceName := service.ObjectMeta.Name
	
	//调用更新api进行更新
	clientset := common.ClientSet(kubeconfig)
	_, err = clientset.CoreV1().Services(namespace).Update(context.TODO(), service, metav1.UpdateOptions{})
	if err != nil {
		return err
	}
	fmt.Println(namespace, serviceName)
	return err
}


四.路由设置

4.1.路由设置

将以下代码放到routers/route.go中


	beego.Router("/svc/v1/List", &controllers.SvcController{}, "*:List")    //读取service列表
	beego.Router("/svc/v1/Create", &controllers.SvcController{}, "*:Create") //创建service
	beego.Router("/svc/v1/ModifyByYaml", &controllers.SvcController{}, "*:ModifyByYaml") //更新yaml配置
	beego.Router("/svc/v1/Yaml", &controllers.SvcController{}, "*:Yaml") //读取yaml配置

五.前端代码

5.1.列表部分html代码

5.1 serviceList.html,放到views/front/page/xkube下

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>service列表</title>
    <meta name="renderer" content="webkit">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <link rel="stylesheet" href="/lib/layui-v2.6.3/css/layui.css" media="all">
    <link rel="stylesheet" href="/css/public.css" media="all">
    <script type="text/javascript" src="/lib/jquery-3.4.1/jquery-3.4.1.min.js"></script>
    <script src="/lib/layui-v2.6.3/layui.js" charset="utf-8"></script>
    <script src="/js/xkube.js?v=1" charset="utf-8"></script>
    <script src="/js/lay-config.js?v=1.0.4" charset="utf-8"></script>

<style type="text/css">
  .layui-table-cell {
    height: auto;
    line-height: 22px !important;
    text-overflow: inherit;
    overflow:ellipsis;
    white-space: normal;
  }
  .layui-table-cell .layui-table-tool-panel li {
    word-break: break-word;
  }
</style>
</head>
<body>
<div class="layuimini-container">
    <div class="layuimini-main">
        <script type="text/html" id="toolbarDemo">
            <div class="layui-btn-container">
                <button class="layui-btn layui-btn-normal layui-btn-sm data-add-btn" lay-event="create"><i class="layui-icon">&#xe61f;</i>创建service</button>
            </div>
        </script>

        <table class="layui-table" id="currentTableId" lay-filter="currentTableFilter"></table>

        <script type="text/html" id="currentTableBar">
            <a class="layui-btn layui-btn-normal layui-btn-sm" lay-event="viewYaml">yaml编辑</a>
        </script>
    </div>
</div>

</body>
<script type="text/html" id="TagTpl">
    {{# if (d.labels != "") { }}
            {{# layui.each(d.labels.split(','), function(index, item){ }}
                {{# if(index == 0) { }}
                        <span>{{ item }}</span>
                {{# }else{ }}
                        <br><span>{{ item }}</span>
                {{# } }}  
            {{# });  }}
    {{# }else{  }}
            <span></span>
    {{# } }}
</script>	
<script type="text/html" id="ipPortTpl">
        <span>{{ d.svcIp }}:{{ d.svcPort }}</span>
</script>	
<script>
var clusterId = getQueryString("clusterId");
if (clusterId == null) {
	clusterId = getCookie("clusterId")
}
    layui.use(['form', 'table','miniTab'], function () {
        var $ = layui.jquery,
            form = layui.form,
            table = layui.table;
            miniTab = layui.miniTab,
            miniTab.listen();

        table.render({
            elem: '#currentTableId',
            url: '/svc/v1/List?clusterId='+clusterId,
            toolbar: '#toolbarDemo',
            defaultToolbar: ['filter', 'exports', 'print', {
                title: '提示',
                layEvent: 'LAYTABLE_TIPS',
                icon: 'layui-icon-tips'
            }],
            parseData: function(res) { //实现加载全部数据后再分页
            	if(this.page.curr) {
            		result=res.data.slice(this.limit*(this.page.curr-1),this.limit*this.page.curr);
            	}else{
            	  result=res.data.slice(0,this.limit);
              }
              return {
              	"code": res.code,
              	"msg":'',
              	"count":res.count,
              	"data":result
              };
            },
            cols: [[
                //{type: "checkbox", width: 50},
                {field: 'serviceName',title: '名称', sort: true},
                {field: 'nameSpace',title: '命名空间', sort: true},
                {field: 'svcType', title: '类型', sort: true},
                {field: 'svcIp', title: 'IP', sort: true,hide:true},
                {field: 'svcPort', title: '端口', sort: true,hide:true},
                {field: '', width:150,title: 'ip端口', sort: true,templet: '#ipPortTpl'},
                {field: 'lanEndpoint',title: '内部端点', sort: true},
                {field: 'wanEndpoint', title: '外部端点', sort: true,hide:true},
                {field: 'labels', title: '标签', sort: true,templet: '#TagTpl'},
                {field: 'createTime', title: '创建时间'},
                {title: '操作', minWidth: 250, toolbar: '#currentTableBar', align: "center"}
            ]],
            //size:'lg',
            limits: [25, 50, 100],
            limit: 25,
            page: true
        });

        /**
         * toolbar监听事件
         */
        table.on('toolbar(currentTableFilter)', function (obj) {
            if (obj.event === 'create') {  // 监听添加操作
                var index = layer.open({
                    title: '创建',
                    type: 2,
                    shade: 0.2,
                    maxmin:true,
                    shadeClose: true,
                    area: ['60%', '90%'],
                    content: '/page/xkube/serviceCreate.html?v=1',
                    //end: function(){
                    //	window.parent.location.reload();//关闭open打开的页面时,刷新父页面
                    //}
                });
                $(window).on("resize", function () {
                    layer.full(index);
                });
            } 
        });

        table.on('tool(currentTableFilter)', function (obj) {
            var data = obj.data;
            if (obj.event === 'viewYaml') {
                var index = layer.open({
                    title: 'yaml',
                    type: 2,
                    shade: 0.2,
                    maxmin:true,
                    shadeClose: true,
                    area: ['45%', '92%'],
                    content: '/page/xkube/serviceYaml.html?clusterId='+clusterId+'&nameSpace='+data.nameSpace+'&serviceName='+data.serviceName,
                });
                $(window).on("resize", function () {
                    layer.full(index);
                });
                return false;
            }
        });


    });
</script>

</body>
</html>
5.2.创建表单html代码

5.2 serviceCreate.html,放到views/front/page/xkube下

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>创建</title>
    <meta name="renderer" content="webkit">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <link rel="stylesheet" href="/lib/layui-v2.6.3/css/layui.css" media="all">
    <link rel="stylesheet" href="/css/public.css" media="all">
    <script type="text/javascript" src="/lib/jquery-3.4.1/jquery-3.4.1.min.js"></script>
    <script src="/lib/layui-v2.6.3/layui.js" charset="utf-8"></script>
    <script src="/js/xkube.js?v=1" charset="utf-8"></script>
    <style>
        body {
            background-color: #ffffff;
        }
    </style>
</head>
<body>
<div class="layuimini-container">
    <div class="layuimini-main">
        <form class="layui-form layui-form-pane" action="">

            <div class="layui-form-item">
                <div class="layui-inline">
                    <label class="layui-form-label">当前集群</label>
                    <div class="layui-input-inline">
                        <select name="clusterId" lay-filter="cluster_Id" lay-search="" id="cluster_Id">
                          <option value="">请选择集群</option>
                        </select>
                    </div>
                    <label class="layui-form-label">命名空间</label>
                    <div class="layui-input-inline">
                      <select name="nameSpace" lay-filter="name_Space" lay-search="" id="name_Space">
      		            </select>
                    </div>
                </div>
            </div>

            <div class="layui-form-item">
                <label class="layui-form-label required">svc名称</label>
                <div class="layui-input-inline">
                    <input type="text" name="serviceName" placeholder="不能为空" value="" class="layui-input">
                </div>
                <label class="layui-form-label">关联</label>
                <div class="layui-input-inline">
                    <input type="text" name="deployName" placeholder="关联的deploy或sts" value="" class="layui-input">
                </div>
            </div>
            <div class="layui-form-item">
                <label class="layui-form-label">svc类型</label>
                <div class="layui-input-inline">
                  <select name="svcType" lay-filter="svcType" lay-search="" id="svcType">
              			<option value="ClusterIp" selected="">ClusterIp</option>
              			<option value="NodePort">NodePort</option>
              			<option value="LoadBalancer">LoadBalancer</option>
  		            </select>
                </div>
                <div id="isHeadlessTpl">
                  <div class="layui-input-inline">
                    <input type="checkbox" name="isHeadless" lay-skin="primary" lay-filter="isHeadless" title="实例间服务发现(Headless Service)">   
                  </div>             
                </div>
            </div>

            <div class="serviceTpl">
              <div class="layui-form-item">
                  <label class="layui-form-label">svc端口</label>
                  <div class="layui-input-inline" style="width:120px">  
                      <input type="text" id="svc_portsName0" name="svc_portsName[]" placeholder="名称" value="" class="layui-input">
                  </div>
                  <div class="layui-input-inline" style="width:120px">  
                      <input type="text" id="svc_port0" name="svc_port[]" placeholder="服务端口" value="" class="layui-input">
                  </div>
                  <div class="layui-input-inline" style="width:120px">  
                      <input type="text" id="svc_targetPort0" name="svc_targetPort[]" placeholder="容器端口" value="" class="layui-input">
                  </div>
                  <div class="layui-input-inline" style="width:70px">  
                    <select id="svc_protocol0" name="svc_protocol[]">
                			<option value="TCP" selected="">TCP</option>
                			<option value="UDP">UDP</option>
    		            </select>
                  </div>
                  <div class="layui-input-inline" style="width:80px">  
                      <button class="layui-btn layui-btn-normal" id="svcaddbtn"><i class="layui-icon layui-icon-add-circle"></i></button>
                  </div>                
              </div> 
            </div>
            <fieldset class="layui-elem-field layui-field-title" style="margin-top: 30px;">
                <legend>标签与注解</legend>
            </fieldset>
            <div class="labelsTpl">
              <div class="layui-form-item">
                  <label class="layui-form-label">标签</label>
                  <div class="layui-input-inline" style="width:150px">  
                      <input type="text" id="labels_key0" name="labels_key[]" placeholder="key" value="" class="layui-input">
                  </div>
                  <div class="layui-input-inline" style="width:150px">  
                      <input type="text" id="labels_value0" name="labels_value[]" placeholder="value" value="" class="layui-input">
                  </div>
                  <div class="layui-input-inline" style="width:80px">  
                      <button class="layui-btn layui-btn-normal" id="newaddbtn"><i class="layui-icon layui-icon-add-circle"></i></button>
                  </div>                
              </div> 
            </div>

            <!--
            <div class="annotationTpl">
              <div class="layui-form-item">
                  <label class="layui-form-label">注解</label>
                  <div class="layui-input-inline" style="width:240px">  
                      <input type="text" id="labels_key0" name="labels_key[]" placeholder="key" value="" class="layui-input">
                  </div>
                  <div class="layui-input-inline" style="width:240px">  
                      <input type="text" id="labels_value0" name="labels_value[]" placeholder="value" value="" class="layui-input">
                  </div>
                  <div class="layui-input-inline">  
                      <button class="layui-btn layui-btn-normal" id="annotateaddbtn"><i class="layui-icon layui-icon-add-circle"></i></button>
                  </div>                
              </div> 
            </div>
            -->

  <br>
            <div class="layui-form-item">
                <div class="layui-input-block">
                    <button class="layui-btn layui-btn-normal" lay-submit lay-filter="saveBtn">确认保存</button>
                </div>
            </div>
        </form>
    </div>
</div>
</body>
<script>
    //标签删除
    var TplIndex = 0;
    function delTpl(id) {
      TplIndex--;
      $("#tpl-"+id).remove();
    }

    //服务删除
    var sTplIndex = 0;
    function delsTpl(id) {
      sTplIndex--;
      $("#stpl-"+id).remove();
    }

    layui.use(['form'], function () {
        var form = layui.form,
            layer = layui.layer,
            $ = layui.$;
            
        $("#isHeadlessTpl").show();
        form.on('select(svcType)', function (data) { 
          if (data.value == "ClusterIp") {
            $("#isHeadlessTpl").show();
          }else{
            $("#isHeadlessTpl").hide();
          }
        });

        //labes add
        $('#newaddbtn').on("click",function(){
          TplIndex++;
          var addTpl =
              '<div class="layui-form-item" id="tpl-'+TplIndex+'">' +
                  '<label class="layui-form-label">标签</label>' +
                  '<div class="layui-input-inline" style="width:150px">' +
        	              '<input type="text" name="labels_key[]" id="labels_key'+TplIndex+'" placeholder="key" value="" class="layui-input">' +
                  '</div>' +    
                  '<div class="layui-input-inline" style="width:150px">' +  
                      '<input type="text" name="labels_value[]" id="labels_value'+TplIndex+'" placeholder="value" value="" class="layui-input">' +
                  '</div>' +
                  '<div class="layui-input-inline" style="width:80px">' +  
                      '<input class="layui-btn layui-btn-normal layui-bg-orange layui-icon" style="width:60px" type="button" id="newDelbtn" value="&#xe616;" οnclick="delTpl('+TplIndex+');" />' +
                  '</div>' +                
              '</div>'
            $('.labelsTpl').append(addTpl);   
          form.render(); 
          return false;   
        });

        //service增加
        $('#svcaddbtn').on("click",function(){
          sTplIndex++;
          var addTpl =
              '<div class="layui-form-item" id="stpl-'+sTplIndex+'">' +
                  '<label class="layui-form-label">svc端口</label>' +
                  '<div class="layui-input-inline" style="width:120px">' +
        	              '<input type="text" name="svc_portsName[]" id="svc_portsName'+sTplIndex+'" placeholder="名称" value="" class="layui-input">' +
                  '</div>' +    
                  '<div class="layui-input-inline" style="width:120px">' +  
                      '<input type="text" name="svc_port[]" id="svc_port'+sTplIndex+'" placeholder="服务端口" value="" class="layui-input">' +
                  '</div>' +
                  '<div class="layui-input-inline" style="width:120px">' +  
                      '<input type="text" name="svc_targetPort[]" id="svc_targetPort'+sTplIndex+'" placeholder="容器端口" value="" class="layui-input">' +
                  '</div>' +
                  '<div class="layui-input-inline" style="width:70px">' +  
                    '<select name="svc_protocol[]" id="svc_protocol'+sTplIndex+'" >' +
                			'<option value="TCP" selected="">TCP</option>' +
                			'<option value="UDP">UDP</option>' +
    		            '</select>' +
                  '</div>' +
                  '<div class="layui-input-inline" style="width:80px">' +  
                      '<input class="layui-btn layui-btn-normal layui-bg-orange layui-icon" style="width:60px" type="button" value="&#xe616;" οnclick="delsTpl('+sTplIndex+');" />' +
                  '</div>' +                
              '</div>'
            $('.serviceTpl').append(addTpl);   
          form.render(); 
          return false;   
        });

        //监听提交
        form.on('submit(saveBtn)', function (data) {
                data.field.serviceName = data.field.serviceName.replace(/^\s*|\s*$/g,""); //替换空格

                if (data.field.svcType != "ClusterIp") {
                  delete data.field.isHeadless;
                }         
                
                //lables 处理
                var labelsArry = [];
                for (var i=0;i<=TplIndex;i++) {
                  //delete data.field.lables_key[i];                  
                  //delete data.field.labels_value[i];
                  var kk = document.getElementById("labels_key"+i).value;
                  var vv = document.getElementById("labels_value"+i).value; 
                  if ( kk != "" && vv != "") {
                    labelsArry.push(
                      {
                        key:kk,
                        value:vv,
                      }
                    )
                  }
                }
                if (labelsArry.length > 0) {
                  data.field.lables = labelsArry;
                }

                //service 处理
                var svcArry = [];
                for (var i=0;i<=sTplIndex;i++) {
                  var kk = document.getElementById("svc_portsName"+i).value;
                  var vv = document.getElementById("svc_port"+i).value; 
                  var ss = document.getElementById("svc_targetPort"+i).value; 
                  var tt = document.getElementById("svc_protocol"+i).value;
                  if ( kk != "" && vv != "" && ss != "") {
                    svcArry.push(
                      {
                        portName:kk,
                        svcPort:vv,
                        targetPort:ss,
                        protocol:tt,
                      }
                    )
                  }
                }
                if (svcArry.length > 0) {
                  data.field.ports = svcArry;
                }

					      console.log(data.field);
			          layer.confirm('确定添加?', {icon: 3, title:'提示',yes: function(index){
                     var index2 = layer.load(0, {shade: false});
                     layer.msg('稍等片刻');
                     $.ajax({
                       url: "/svc/v1/Create",
                       type: "post",
                       data: JSON.stringify(data.field),
                       dataType: "json",
                       success: function (resp) {
                                layer.close(index2);
                                 if(resp.code == 0){
                                    layer.msg('添加成功', {icon: 1});
									                  //window.location.reload();
                                 }else{
                                    layer.msg(resp.msg,{icon:2});
                                 }
                        }
                      });		  	  
                  },
                  cancel: function(index, layero){ 
                    layer.close(index);
                    layer.close(index2);
		                console.log("不操作");
                  } 
                });
              return false;
        });

    });
</script>
</body>
</html>
5.3.显示yaml配置的html代码

5.3 serviceYaml.html,放到views/front/page/xkube下

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>yaml编辑</title>
    <meta name="renderer" content="webkit">
    <link rel="stylesheet" href="/lib/layui-v2.6.3/css/layui.css" media="all">
    <link rel="stylesheet" href="/css/public.css" media="all">
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
		<link
			rel="stylesheet"
			data-name="vs/editor/editor.main"
			href="/monaco-editor/min/vs/editor/editor.main.css"
		/>
    <script src="/js/xkube.js?v=1" charset="utf-8"></script>
    <script type="text/javascript" src="/lib/jquery-3.4.1/jquery-3.4.1.min.js"></script>
</head>
<body>
<div class="layuimini-container">
    <div class="layuimini-main">
        <div id="container" style="width: 100%; height:460px; border: 1px solid grey"></div>
        <br>
        <fieldset class="table-search-fieldset">
            <legend></legend>
            <button class="layui-btn layui-btn-sm" id="SaveBtn" >保存更新</button>
            <button class="layui-btn layui-btn-normal layui-btn-sm" id="DownLoadBtn">下载</button>
        </fieldset>   
    </div>
</div>
<script src="/lib/layui-v2.6.3/layui.js" charset="utf-8"></script>
<script>
	var require = { paths: { vs: '/monaco-editor/min/vs' } };
</script>
<script src="/monaco-editor/min/vs/loader.js"></script>
<script src="/monaco-editor/min/vs/editor/editor.main.nls.js"></script>
<script src="/monaco-editor/min/vs/editor/editor.main.js"></script>
<script>
    layui.use(['form', 'table','code'], function () {
        var $ = layui.jquery;
        var form = layui.form,
            layer = layui.layer;
        
    		var editor = monaco.editor.create(document.getElementById('container'), {
    			value: '',
    			language: 'yaml',
    			//automaticLayout: true,
    			minimap: {enabled: false},
    			wordWrap: 'on',
    			theme: 'vs-dark'
    		});

    		$.ajax({
    		    url: "/svc/v1/Yaml"+location.search,
    		    type: "GET",
    		    success: function (resp) {
    			  //codeyaml = resp;
    			  editor.setValue(resp);
    			}
    		});	

        $('#SaveBtn').on("click",function(){
              var yamlBody = editor.getValue();
              yamlBody = yamlBody.replace(/%/g,"%25");
              layer.confirm('确定修改?', {icon: 3, title:'提示',yes: function(index){
                  $.ajax({
                   url: "/svc/v1/ModifyByYaml"+location.search,
                   type: "POST",
                   data: yamlBody,
                   dataType: "json",
                   success: function (resp) {
                      layer.msg(resp.msg);
                      console.log(resp);
                    }
                  });
                  //console.log(editor.getValue());
                  //layer.msg('ok');
              },
              cancel: function(index, layero){ 
                  layer.close(index);
              } 
            });
        });

        $('#DownLoadBtn').on("click",function(){
              var serviceName = getQueryString("serviceName");
              ExportRaw(serviceName+'.yaml',editor.getValue());
        });
    });
</script>

</body>
</html>

六.完整代码

6.1.控制器service.go的完整代码

6.1 service.go,放到contrillers下

// service.go
package controllers

import (
	//"encoding/json"
	//"fmt"
	"log"
	m "myk8s/models"
	"strings"

	beego "github.com/beego/beego/v2/server/web"
)

type SvcController struct {
	beego.Controller
}

func (this *SvcController) List() {
	clusterId := this.GetString("clusterId")
	nameSpace := this.GetString("nameSpace")
	serviceName := this.GetString("serviceName")
	labels := this.GetString("labels")
	labelsKV := strings.Split(labels, ":")
	var labelsKey, labelsValue string
	if len(labelsKV) == 2 {
		labelsKey = labelsKV[0]
		labelsValue = labelsKV[1]
	}
	svcList, err := m.SvcList(clusterId, nameSpace, serviceName, labelsKey, labelsValue)
	msg := "success"
	code := 0
	count := len(svcList)
	if err != nil {
		log.Println(err)
		msg = err.Error()
		code = -1
	}

	this.Data["json"] = &map[string]interface{}{"code": code, "msg": msg, "count": count, "data": &svcList}
	this.ServeJSON()
}

func (this *SvcController) Create() {
	clusterId := this.GetString("clusterId")
	code := 0
	msg := "success"
	//log.Println(string(this.Ctx.Input.RequestBody))
	err := m.SvcCreate(clusterId, this.Ctx.Input.RequestBody)
	if err != nil {
		code = -1
		msg = err.Error()
		log.Printf("[ERROR] configmap Create Fail:%s\n", err)
	}
	this.Data["json"] = &map[string]interface{}{"code": code, "msg": msg}
	this.ServeJSON()
}

func (this *SvcController) ModifyByYaml() {
	clusterId := this.GetString("clusterId")
	//nameSpace := gp.Get("nameSpace").String()
	log.Println(string(this.Ctx.Input.RequestBody))
	bodyByte := []byte(strings.ReplaceAll(string(this.Ctx.Input.RequestBody), "%25", "%"))
	err := m.SvcYamlModify(clusterId, bodyByte)
	if err != nil {
		log.Println(err)
	}
	this.Data["json"] = &map[string]interface{}{"code": 0, "msg": "ok"}
	this.ServeJSON()
}

func (this *SvcController) Yaml() {
	clusterId := this.GetString("clusterId")
	nameSpace := this.GetString("nameSpace")
	serviceName := this.GetString("serviceName")

	yamlStr, _ := m.GetSvcYaml(clusterId, nameSpace, serviceName)
	this.Ctx.WriteString(yamlStr)
}

6.2.模型serviceModel.go的完整代码

6.2 serviceModel.go,放到models下

// serviceModel.go
package models

import (
	"context"
	"fmt"
	"log"
	"myk8s/common"
	"strings"

	"encoding/json"

	"github.com/tidwall/gjson"
	//"k8s.io/apimachinery/pkg/api/errors"

	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	yamlutil "k8s.io/apimachinery/pkg/util/yaml"

	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/util/intstr"

	"sigs.k8s.io/yaml"
)

type Service struct {
	ServiceName string `json:"serviceName"` //服务名称
	NameSpace   string `json:"nameSpace"`
	Labels      string `json:"labels"`
	SvcType     string `json:"svcType"`
	SvcIp       string `json:"svcIp"`
	SvcPort     string `json:"svcPort"`
	LanEndpoint string `json:"lanEndpoint"` //内部端点
	WanEndpoint string `json:"wanEndpoint"` //外部端点
	CreateTime  string `json:"createTime"`
}

type ServicePort struct {
	PortName   string `json:"portName"`
	SvcPort    int32  `json:"svcPort"`
	TargetPort int32  `json:"targetPort"`
}

func SvcList(kubeconfig, namespace, serviceName string, labelsKey, labelsValue string) ([]Service, error) {
	clientset := common.ClientSet(kubeconfig)
	if namespace == "" {
		//namespace = corev1.NamespaceDefault
		namespace = corev1.NamespaceAll
	}
	//var selector labels.Selector

	var listOptions = metav1.ListOptions{}
	if labelsKey != "" && labelsValue != "" {
		listOptions = metav1.ListOptions{LabelSelector: fmt.Sprintf("%s=%s", labelsKey, labelsValue)}
	}

	svcList, err := clientset.CoreV1().Services(namespace).List(context.TODO(), listOptions)
	if err != nil {
		log.Printf("list service error:%v\n", err)
	}

	//fmt.Println("svc count:", len(svcList.Items))
	var bbb = make([]Service, 0)
	for _, svc := range svcList.Items {
		//搜索
		if serviceName != "" {
			if !strings.Contains(svc.Name, serviceName) {
				continue
			}
		}
		//fmt.Printf("name: %s\n", svc.Name)
		var labelsStr string
		for kk, vv := range svc.ObjectMeta.Labels {
			labelsStr += fmt.Sprintf("%s:%s,", kk, vv)
		}
		if len(labelsStr) > 0 {
			labelsStr = labelsStr[0 : len(labelsStr)-1]
		}
		var wanEndPoint string
		svcType := fmt.Sprintf("%s", svc.Spec.Type)
		if svcType == "LoadBalancer" {
			wanEndPoint = fmt.Sprintf("%s:%d", svc.Status.LoadBalancer.Ingress[0].IP, svc.Spec.Ports[0].Port)
		}
		lanEndPoint := ""
		var svcPort string
		if len(svc.Spec.Ports) > 0 {
			vsapp := svc.Spec.Selector["app"]
			if vsapp == "" {
				vsapp = svc.Spec.Selector["k8s-app"]
			}
			for _, vv := range svc.Spec.Ports {
				//lanEndPoint += fmt.Sprintf("%s:%s:%s\n", svc.Spec.Selector["app"], svc.Spec.Ports[0].TargetPort.StrVal, svc.Spec.Ports[0].Protocol)
				//svcPort += fmt.Sprintf("%s:", svc.Spec.Ports[0].Port)
				var vport string
				if vv.TargetPort.Type == 0 {
					vport = fmt.Sprintf("%d", vv.TargetPort.IntVal)
				} else {
					vport = fmt.Sprintf("%s", vv.TargetPort.StrVal)
				}
				lanEndPoint += fmt.Sprintf("%s:%s:%s,", vsapp, vport, vv.Protocol)
				svcPort += fmt.Sprintf("%d,", vv.Port)
			}
			if len(svcPort) > 0 {
				svcPort = svcPort[0 : len(svcPort)-1]
			}
			lanEndPoint = lanEndPoint[0 : len(lanEndPoint)-1]
		}
		Items := &Service{
			ServiceName: svc.Name,
			NameSpace:   svc.Namespace,
			SvcType:     svcType,
			SvcIp:       svc.Spec.ClusterIP,
			Labels:      labelsStr,
			SvcPort:     svcPort,
			LanEndpoint: lanEndPoint,
			WanEndpoint: wanEndPoint,
			CreateTime:  svc.CreationTimestamp.Format("2006-01-02 15:04:05"),
		}
		bbb = append(bbb, *Items)
	}
	return bbb, err
}

func SvcCreate(kubeconfig string, bodys []byte) error {
	gp := gjson.ParseBytes(bodys)
	clusterId := gp.Get("clusterId").String()
	if kubeconfig == "" {
		kubeconfig = clusterId
	}
	serviceName := gp.Get("serviceName").String()
	nameSpace := gp.Get("nameSpace").String()
	svcType := gp.Get("svcType").String()
	deployName := gp.Get("deployName").String()
	isHeadless := gp.Get("isHeadless").Str

	var labelsMap = make(map[string]string)
	labelsMap["app"] = serviceName
	for _, vv := range gp.Get("lables").Array() {
		labelsMap[vv.Get("key").Str] = vv.Get("value").Str
	}

	selectApp := serviceName
	if deployName != "" {
		selectApp = deployName
	}

	var serviceType corev1.ServiceType
	switch svcType {
	case "NodePort":
		serviceType = corev1.ServiceTypeNodePort
	case "LoadBalancer":
		serviceType = corev1.ServiceTypeLoadBalancer
	default:
		serviceType = corev1.ServiceTypeClusterIP
	}

	if isHeadless == "on" {
		serviceType = corev1.ServiceTypeClusterIP
	}

	svcInstance := &corev1.Service{
		ObjectMeta: metav1.ObjectMeta{
			Name:      serviceName,
			Namespace: nameSpace,
			Labels:    labelsMap,
		},
		Spec: corev1.ServiceSpec{
			Selector: map[string]string{
				"app": selectApp,
			},
			Type: serviceType,
		},
	}
	if isHeadless == "on" {
		svcInstance.Spec.ClusterIP = corev1.ClusterIPNone
	}

	ports := gp.Get("ports").Array()
	var svcPorts = make([]corev1.ServicePort, 0)
	for _, vv := range ports {
		var svcProtocol corev1.Protocol
		if vv.Get("protocol").Str == "UDP" {
			svcProtocol = corev1.ProtocolUDP
		} else {
			svcProtocol = corev1.ProtocolTCP
		}
		svcPort := &corev1.ServicePort{
			Name:       vv.Get("portName").Str,
			Port:       int32(vv.Get("svcPort").Int()),
			Protocol:   svcProtocol,
			TargetPort: intstr.FromInt32(int32(vv.Get("targetPort").Int())),
		}
		svcPorts = append(svcPorts, *svcPort)
	}
	svcInstance.Spec.Ports = svcPorts

	clientset := common.ClientSet(kubeconfig)
	_, err := clientset.CoreV1().Services(nameSpace).Create(context.TODO(), svcInstance, metav1.CreateOptions{})
	if err != nil {
		return err
	}
	return nil
}

func SvcYamlModify(kubeconfig string, yamlData []byte) error {
	data, err := yamlutil.ToJSON(yamlData)
	if err != nil {
		return err
	}
	service := &corev1.Service{}
	err = json.Unmarshal(data, service)
	if err != nil {
		return err
	}

	namespace := service.ObjectMeta.Namespace
	serviceName := service.ObjectMeta.Name
	clientset := common.ClientSet(kubeconfig)
	_, err = clientset.CoreV1().Services(namespace).Update(context.TODO(), service, metav1.UpdateOptions{})
	if err != nil {
		return err
	}
	fmt.Println(namespace, serviceName)
	return err
}

func GetSvcYaml(kubeconfig, namespace, serviceName string) (string, error) {
	servicesClient := common.ClientSet(kubeconfig).CoreV1().Services(namespace)
	service, err := servicesClient.Get(context.TODO(), serviceName, metav1.GetOptions{})
	serviceUnstructured, err := runtime.DefaultUnstructuredConverter.ToUnstructured(service)
	if err != nil {
		return "", err
	}
	yamlBytes, err := yaml.Marshal(serviceUnstructured)
	if err != nil {
		return "", err
	}
	return string(yamlBytes), nil
}

七.效果图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1650953.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

湖仓一体 - Apache Arrow的那些事

湖仓一体 - Apache Arrow的那些事 Arrow是高性能列式内存格式标准。它的优势&#xff1a;高效计算&#xff1a;所有列存的通用优势&#xff0c;CPU缓存友好、SIMD向量化计算友好等&#xff1b;零序列化/反序列化&#xff1a;arrow的任何数据结构都是一段连续的内存&#xff0c;…

基于单片机的无线数据传输系统设计

摘要:基于单片机的无线数据传输系统的设计,实现了温度和湿度的自动采集、无线通讯和报警功能。该系统包括了LCD1602显示电路、DHT11温湿度采集电路等,完成了基于无线数据传输的方法来实现温湿度的采集。 关键词:温湿度检测;N RF 24 L 01;单片机 0 引言 随着科技水平的提高,…

将矩阵按对角线排序(Lc1329)——排序

矩阵对角线 是一条从矩阵最上面行或者最左侧列中的某个元素开始的对角线&#xff0c;沿右下方向一直到矩阵末尾的元素。例如&#xff0c;矩阵 mat 有 6 行 3 列&#xff0c;从 mat[2][0] 开始的 矩阵对角线 将会经过 mat[2][0]、mat[3][1] 和 mat[4][2] 。 给你一个 m * n 的整…

Mac 链接 HP 136w 打印机步骤

打开 WI-FI 【1】打开打印机左下角Wi-Fi网络设计【或者点击…按钮进入WI-FI菜单】&#xff0c;找到NetWork选项OK进入&#xff1b; 【2】设置WI-FI选项&#xff1a;在菜单内找到Wi-Fi选项OK进入&#xff1b; 【3】在菜单内找到Wi-Fi Direct选项OK进入&#xff1b; 【4】在菜单…

flutter开发实战-webview_flutter 4.x版本使用

flutter开发实战-webview_flutter 4.x版本使用 在之前使用的webview_flutter版本是3.x的&#xff0c;升级到4.x后&#xff0c;使用方式有所变化。 一、webview_flutter 在工程的pubspec.yaml中引入插件 webview_flutter: ^4.4.2二、使用webview_flutter 在4.x版本中&#…

使用mxnet中的img2rec.py制作rec数据集

源码链接&#xff1a;mxnet/tools/im2rec.py at master apache/mxnet GitHub 重点关注入参函数即可&#xff0c; def parse_args():"""Defines all arguments.Returns-------args object that contains all the params"""parser argparse.A…

每日OJ题_贪心算法三②_力扣553. 最优除法

目录 力扣553. 最优除法 解析代码 力扣553. 最优除法 553. 最优除法 难度 中等 给定一正整数数组 nums&#xff0c;nums 中的相邻整数将进行浮点除法。例如&#xff0c; [2,3,4] -> 2 / 3 / 4 。 例如&#xff0c;nums [2,3,4]&#xff0c;我们将求表达式的值 "…

人大金仓V8R6迁移mysql8.0

人大金仓数据库迁移mysql mysql版本&#xff1a;mysql 8.0.22 人大金仓版本;KingbaseES V008R006C008B0014 on x64 打开数据迁移工具 等待执行完成后使用命令窗口中提示的地址在浏览器中打开&#xff1a; 登录。此处登录不用修改任何信息&#xff0c;点击登录即可 新建源数…

便携式显示器芯片组-->LDR6282 +RTD2556T(1HDMI+2Typec(DP) 点1080P eDP屏)

RTD2555TLDR6282实现 1VGA1MiniHDMI2Typec(DP In)点1080P eDP屏 LDR6282 PD 芯片负责通过CC线与电脑沟通&#xff0c;让电脑送出DP显示信号和协商相关的Typec充电电压。 Scaler负责接收两路Typec-->DP信号&#xff0c;Typec正反插时LDR6282会反馈给RTD2555T相应的IO状态&a…

Python网络协议socket

01 协议基础 01 网络协议 协议&#xff1a;一种规则 网络协议&#xff1a;网络规则&#xff0c;一种在网络通信中的数据包的数据规则 02 TCP/IP协议 osi模型 tcp/ip协议 03 tcp协议 TCP协议提供了一种端到端的、基于连接的、可靠的通信服务。 三次握手 创建连接 四次挥手…

证券基金信创联盟研讨会:YashanDB分享金融核心数据库技术实践

4月26日&#xff0c;由证券基金行业信息技术应用创新联盟主办、WG3稽核风控系统工作组承办、国信证券股份有限公司协办的信创联盟2024年度系列研讨会第三期-稽核风控系统信创实践成功举办。国内头部企业国信证券、申万宏源证券、信达证券、国金证券、广发证券等单位共计300余人…

必应bing国内广告怎么做付费推广,提升产品曝光?

必应Bing作为微软旗下重要的搜索引擎平台&#xff0c;拥有着不可忽视的用户基础和市场潜力。对于寻求拓宽市场、提高品牌知名度的企业而言&#xff0c;利用必应Bing进行付费推广无疑是明智之选。通过必应Bing国内广告进行高效付费推广&#xff0c;助您轻松提升产品曝光度。 一…

TC6291C 是一款电流模式升压型DC-DC转换器芯片

一般概述 TC6291C是一款电流模式升压型DC-DC转换器。其脉宽调制电路&#xff0c;内置0.2Q功率场效应管使这个调节器具有高功率效率。内部补偿网络也减少了多达6个的外部元件。误差信号放大器的同相输入端连接到0.6V精密基准电压&#xff0c;内部软启动功能可以减小瞬间突…

Python轴承故障诊断 (18)基于CNN-TCN-Attention的创新诊断模型

往期精彩内容&#xff1a; Python-凯斯西储大学&#xff08;CWRU&#xff09;轴承数据解读与分类处理 Python轴承故障诊断 (一)短时傅里叶变换STFT Python轴承故障诊断 (二)连续小波变换CWT_pyts 小波变换 故障-CSDN博客 Python轴承故障诊断 (三)经验模态分解EMD_轴承诊断 …

关于c++ 中 string s { ‘a‘ , ‘b‘ , ‘c‘ , ‘d‘ } 的方式的构造过程

&#xff08;1&#xff09;这样的构造方式不常见&#xff0c;但也确实 STL 库提供了这样的构造函数 &#xff08;2&#xff09;以反汇编分析这行代码 &#xff08;3&#xff09;谢谢阅读

json-server的安装和使用

json-server介绍 json-server是可以把本地当做服务器&#xff0c;然后axios向本地区发送请求&#xff0c;并且不会出现跨域的问题&#xff0c;若是等不及后端数据&#xff0c;可以用这个模拟假数据 json-server安装及使用 【json-server网址】https://www.npmjs.com/package/…

泰迪智能科技中职大数据实验室建设(职业院校大数据实验室建设指南)

职校大数据实验室是职校校园文化建设的重要部分&#xff0c;大数据实训室的建设方案应涵盖多个方面&#xff0c;包括硬件设施的配备、软件环境的搭建、课程资源的开发、师资力量的培养以及实践教学体系的完善等。 打造特色&#xff0c;对接生产 社会经济与产业的…

Java 8特性(一) 之 手写Stream流filter、map和forEach方法

Java 8特性&#xff08;一&#xff09; 之 手写Stream流filter、map和forEach方法 今天看了一下Java 8的Stream流&#xff0c;学习了一下函数式编程&#xff0c;这才感受函数式编程如此爽&#xff0c;之前就使用过ES8.7.1的函数式编程&#xff0c;当时就在想啥时候咱也能写出这…

【边东随笔】北美鳄龟的生存智慧:细心 | 信心 | 狠心 | 耐心

非常谨慎&#xff0c;在水域中会先找到躲避将自身安置于有利地形 ( 细心 &#xff09;。 浮出水面换气&#xff0c;水体稍有异动就会退回水中&#xff0c;优秀掠食者对自身优势牢牢的把握&#xff08; 信心 &#xff09;。 非常优雅&#xff0c;猎食动作不存在任何花里胡哨&a…

前端 Android App 上架详细流程 (Android App)

1、准备上架所需要的材料 先在需要上架的官方网站注册账号。提前把手机号&#xff0c;名字&#xff0c;身份证等等材料准备好&#xff0c;完成开发者实名认证&#xff1b;软著是必要的&#xff0c;提前准备好&#xff0c;软著申请时间比较长大概需要1-2周时间才能下来&#xf…