【云原生开发】K8S集群管理后端开发设计与实现

news2024/11/8 10:54:55

在这里插入图片描述

✨✨ 欢迎大家来到景天科技苑✨✨

🎈🎈 养成好习惯,先赞后看哦~🎈🎈

🏆 作者简介:景天科技苑
🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。
🏆《博客》:Python全栈,Golang开发,云原生开发,PyQt5和Tkinter桌面开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi,flask等框架,云原生K8S,linux,shell脚本等实操经验,网站搭建,数据库等分享。

所属的专栏:云原生开发
景天的主页:景天科技苑

在这里插入图片描述

文章目录

  • K8S集群管理后端开发设计与实现
    • 1、incluster命名空间的检测与创建
    • 2、集群管理的路由配置
    • 3、实现添加更新集群功能
    • 4、 结构体转化成map工具包
    • 5、删除集群
    • 6、集群列表查询
    • 7、获取集群详情

K8S集群管理后端开发设计与实现

集群管理包含在前端添加,更新,删除,查询所有集群,查询集群详情等功能实现。

1、incluster命名空间的检测与创建

根据之前的架构规划,我们的元数据存储在inCluster这个K8S集群中的krm名称空间,程序启动的时候,先检查krm这个命名空间是否存在,不存在的话我们就创建这个命名空间
在这里插入图片描述

设个默认值,并可以通过环境变量获取
在这里插入图片描述

检查命名空间需要在程序运行前执行,因此我们需要创建个controller,来做初始化检查操作

package initcontroller

import (
    _ "jingtian/krm-backend/config" //调用里面的init函数,将日志格式初始化
    "jingtian/krm-backend/utils/logs"
)

// 只写个init函数,用来检查配置
func init() {
    //这里面需要初始化incluster的kubeconfig,创建客户端。创建元数据的命名空间
    logs.Debug(nil, "初始化incluster数据...")
    // 1. 通过kubeconfig创建client-go客户端
    // 2. 检查元数据命名空间是否创建,如有,提醒下元数据命名空间未创建。如没有,就创建命名空间
    MetadataInit()
}

在这里插入图片描述

在initcontroller包里面,创建个initcluster.go,用来检测命名空间是否创建,如未创建,即刻创建。

package initcontroller

import (
    "context"
    "jingtian/krm-backend/config"
    "jingtian/krm-backend/utils/logs"
    corev1 "k8s.io/api/core/v1"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/client-go/kubernetes"
    "k8s.io/client-go/tools/clientcmd"
)

func MetadataInit() {
    logs.Debug(nil, "初始化元数据命名空间")
    // 1. 初始化config实例
    // masterUrl就是离我们主节点的ip地址和端口号,我们在kubeconfig文件中有了,所以可以省略
    kubeconfig, err := clientcmd.BuildConfigFromFlags("", "meta.kubeconfig")
    //要想正常应用我们的服务,必须能够实例化成功kubeconfig,要不然后面所有的功能都无法使用,所以这里直接报panic即可
    if err != nil {
        logs.Error(map[string]interface{}{"msg": err.Error()}, "inCluster kubeconfig加载失败")
        panic(err.Error())
    }

    // 2. 创建客户端工具 create the clientset
    clientset, err := kubernetes.NewForConfig(kubeconfig)
    //这个客户端工具如果生成失败的话,后面的操作也无法完成,所以这里也报panic即可
    if err != nil {
        logs.Error(map[string]interface{}{"msg": err.Error()}, "inCluster客户端创建失败")
        panic(err.Error())
    }

    // 获取K8S的版本号
    // ServerVersion() (*version.Info, error)
    inClusterVersion, _ := clientset.Discovery().ServerVersion()
    // 3.检查命名空间是否存在
    _, err = clientset.CoreV1().Namespaces().Get(context.TODO(), config.MetadataNamespace, metav1.GetOptions{})
    if err != nil {
        // 不存在元数据命名空间
        logs.Info(nil, "元数据命名空间不存在,准备创建....")
        // 创建元数据命名空间
        var metadataNamespace corev1.Namespace
        metadataNamespace.Name = config.MetadataNamespace
        _, err = clientset.CoreV1().Namespaces().Create(context.TODO(), &metadataNamespace, metav1.CreateOptions{})
        if err != nil {
            logs.Error(map[string]interface{}{"msg": err.Error()}, "元数据命名空间创建失败")
            panic(err.Error())
        }

        logs.Info(map[string]interface{}{"Namespace": config.MetadataNamespace, "inCluster版本": inClusterVersion.String()}, "元数据命名空间创建成功")
    } else {
        // 已经存在namespace
        logs.Info(map[string]interface{}{"Namespace": config.MetadataNamespace, "inCluster版本": inClusterVersion.String()}, "元数据命名空间已存在")
    }
}

在这里插入图片描述

运行程序,元数据命名空间创建成功
在这里插入图片描述

在K8S集群查看命名空间,可见命名空间创建成功
在这里插入图片描述

2、集群管理的路由配置

我们得路由要在routers目录下去管理
在这里插入图片描述

并且通过routers.go文件实现路由的注册
在这里插入图片描述

创建集群管理的路由和控制器

routers.go

// Package routers 路由层 管理程序的路由信息
package routers

import (
    "github.com/gin-gonic/gin"
    "jingtian/krm-backend/routers/auth"
    "jingtian/krm-backend/routers/cluster"
)

// RegisterRouters 需要将main.go里面的路由引擎r传过来
// 写个注册路由的方法
func RegisterRouters(r *gin.Engine) {
    //登录的路由配置
    //1. 登录: login
    //2. 登出: loginout
    //3. 路由分组 /api/auth/login  /api/auth/loginout
    apiGroup := r.Group("/api")
    auth.RegisterSubRouter(apiGroup)
    cluster.RegisterSubRouter(apiGroup)

}

在这里插入图片描述

cluster.go

package cluster

import (
    "github.com/gin-gonic/gin"
    "jingtian/krm-backend/controllers/cluster"
)

// 实现添加集群的接口
func add(authGroup *gin.RouterGroup) {
    //具体逻辑写到控制器controller里面
    authGroup.POST("/add", cluster.Add)
}

// 实现更新集群的接口
func update(authGroup *gin.RouterGroup) {
    //具体逻辑写到控制器controller里面
    authGroup.POST("/update", cluster.Update)
}

// 删除集群
func deleteCluster(clusterGroup *gin.RouterGroup) {
    clusterGroup.GET("/delete", cluster.DeleteCluster)
}

// 获取集群信息
func get(clusterGroup *gin.RouterGroup) {
    clusterGroup.GET("/get", cluster.Get)
}

// 列出所有集群
func list(clusterGroup *gin.RouterGroup) {
    clusterGroup.GET("/list", cluster.List)
}

// RegisterSubRouter 认证子路由
func RegisterSubRouter(g *gin.RouterGroup) {
    //配置登录功能路由策略
    clusterGroup := g.Group("/cluster")
    add(clusterGroup)
    update(clusterGroup)
    deleteCluster(clusterGroup)
    get(clusterGroup)
    list(clusterGroup)

}

在这里插入图片描述

控制器
在这里插入图片描述

由于除了登录和登出的其他接口,都要携带token才能访问,所以我们先登录,生成token,携带token发出请求
在这里插入图片描述

携带token请求测试
在这里插入图片描述

请求能走到添加集群的控制器
在这里插入图片描述

3、实现添加更新集群功能

怎么实现添加集群呢? 前端可能会让我们输入一些集群的信息,比如集群的名字,集群的ID,kubeconfig等。前端把这些信息提交给后端,然后后端再根据这些信息来创建集群
gin框架可以将前端传来的json数据和结构体进行绑定,然后通过结构体进行创建资源等其他操作。

我们先创建个结构体,来声明创建一个集群所需要的字段。我们将结构体的定义放在controller/cluster.go中

package cluster

import (
    "k8s.io/client-go/kubernetes"
    "k8s.io/client-go/tools/clientcmd"
)

// MyClusterInfo 主要用于查询操作
type MyClusterInfo struct {
    Id          string `json:"id"`
    DisplayName string `json:"displayName"` //集群的别名
    City        string `json:"city"`
    District    string `json:"district"`
}

// MyClusterStatus 定义一个结构体,用于描述集群的状态
// 集群可用,状态就是Active 不可用,状态就是InActive
type MyClusterStatus struct {
    MyClusterInfo
    Version string `json:"version"`
    Status  string `json:"status"`
}

// MyClusterConfig 定义一个结构体,用于描述创建集群所用的配置信息
type MyClusterConfig struct {
    MyClusterInfo
    Kubeconfig string `json:"kubeconfig"` //集群的配置资源
}

// GetClusterStatus 结构体的方法,用于判断集群的状态
func (cfg *MyClusterConfig) GetClusterStatus() (MyClusterStatus, error) {
    // 判断集群是否是正常
    clusterStatus := MyClusterStatus{}
    clusterStatus.MyClusterInfo = cfg.MyClusterInfo
    //此时,需要检测集群实时状态,所以需要重新连接集群
    // 创建一个clientset,从前端传来的kubeconfig字符串来连接集群,这也是out-cluster方式创建客户端
    // func RESTConfigFromKubeConfig(configBytes []byte) (*restclient.Config, error)
    // 接收的参数是字节类型
    restConfig, err := clientcmd.RESTConfigFromKubeConfig([]byte(cfg.Kubeconfig))
    if err != nil {
        clusterStatus.Version = "Unavailable Version"
        clusterStatus.Status = "InActive"
        return clusterStatus, err
    }
    clientset, err := kubernetes.NewForConfig(restConfig)
    if err != nil {
        clusterStatus.Version = "Unavailable Version"
        clusterStatus.Status = "InActive"
        return clusterStatus, err
    }
    //客户端工具创建成功后,获取版本信息
    serverVersion, err := clientset.Discovery().ServerVersion()
    if err != nil {
        return clusterStatus, err
    }
    //集群正常就将集群的版本和状态返回
    clusterVersion := serverVersion.String()
    clusterStatus.Version = clusterVersion
    clusterStatus.Status = "Active"
    return clusterStatus, nil
}

在这里插入图片描述

由于添加和更新集群,所用的参数差不多,所以我们将添加和更新写到一个函数中AddOrUpdate.go,根据传参,来区分是添加还是更新

package cluster

import (
    "context"
    "fmt"
    "github.com/gin-gonic/gin"
    "jingtian/krm-backend/config"
    "jingtian/krm-backend/utils"
    "jingtian/krm-backend/utils/logs"
    corev1 "k8s.io/api/core/v1"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "net/http"
)

// method: update,  create
func addOrUpdate(c *gin.Context, method string) {
    var arg string
    if method == "Create" {
        arg = "添加"
    } else if method == "Update" {
        arg = "更新"
    }
    // 声明一个集群配置
    clusterConfig := MyClusterConfig{}
    returnData := config.NewReturnData()
    if err := c.ShouldBindJSON(&clusterConfig); err != nil {
        msg := arg + "集群的配置信息不完整: " + err.Error()
        returnData.Status = 400
        returnData.Msg = msg
        c.JSON(200, returnData)
        return
    }
    // 判断集群是否正常,写个函数去连接集群,如果能获取到集群的版本,说明集群是正常的
    // 这个函数写在哪呢,应该写在MyClusterConfig这个结构体的方法中
    clusterStatus, err := clusterConfig.GetClusterStatus()
    if err != nil {
        msg := "无法获取集群信息: " + err.Error()
        returnData.Status = 400
        returnData.Msg = msg
        c.JSON(http.StatusOK, returnData)
        logs.Error(map[string]interface{}{"error": err.Error()}, arg+"集群失败,无法获取集群信息")
        return
    }
    logs.Info(map[string]interface{}{"集群名称": clusterConfig.DisplayName, "集群ID": clusterConfig.Id}, "开始"+arg+"集群")

    // 创建一个集群配置的secret 保存集群信息
    var clusterConfigSecret corev1.Secret
    clusterConfigSecret.Name = clusterConfig.Id
    clusterConfigSecret.Labels = make(map[string]string)
    clusterConfigSecret.Labels[config.ClusterConfigSecretLabelKey] = config.ClusterConfigSecretLabelValue

    // 添加注释,保存集群的配置信息
    clusterConfigSecret.Annotations = make(map[string]string)
    // 把集群的状态结构体转成map。我们专门写个工具函数来实现结构体与map的转换
    m, err := utils.Struct2Map(clusterStatus)
    if err != nil {
        logs.Error(nil, err.Error())
        return
    }
    clusterConfigSecret.Annotations = m

    // 保存kubeconfig,我们保存到StringData里面的。我们点进去secret查看
    //里面有Data和StringData两种,Data是需要加密的才能使用,StringData我们可以传字符串,当我们查看secret的时候自动加密
    //Data map[string][]byte `json:"data,omitempty" protobuf:"bytes,2,rep,name=data"`
    //StringData map[string]string `json:"stringData,omitempty" protobuf:"bytes,4,rep,name=stringData"`
    //如果在 data 和 stringData 中设置了同一个字段,则使用来自 stringData 中的值

    clusterConfigSecret.StringData = make(map[string]string)
    clusterConfigSecret.StringData["kubeconfig"] = clusterConfig.Kubeconfig

    // 创建secret
    if method == "Create" {
        _, err = config.InClusterClientSet.CoreV1().Secrets(config.MetadataNamespace).Create(context.TODO(), &clusterConfigSecret, metav1.CreateOptions{})
    } else if method == "Update" {
        //更新
        _, err = config.InClusterClientSet.CoreV1().Secrets(config.MetadataNamespace).Update(context.TODO(), &clusterConfigSecret, metav1.UpdateOptions{})
    }
    if err != nil {
        // 说明创建失败
        logs.Error(map[string]interface{}{"集群ID": clusterConfig.Id, "集群名字:": clusterConfig.DisplayName, "msg": err.Error()}, "集群"+arg+"失败")
        msg := arg + "集群失败: " + err.Error()
        returnData.Msg = msg
        returnData.Status = 400
        c.JSON(200, returnData)
        return
    }
    //创建or更新成功
    //map需要先初始化
    config.ClusterKubeconfig = make(map[string]string)
    config.ClusterKubeconfig[clusterConfig.Id] = clusterConfig.Kubeconfig
    fmt.Println("当前集群配置:", config.ClusterKubeconfig)
    logs.Info(map[string]interface{}{"集群ID": clusterConfig.Id, "集群名字:": clusterConfig.DisplayName}, "集群"+arg+"成功")
    returnData.Status = 200
    returnData.Msg = arg + "成功"
    c.JSON(200, returnData)
}

在这里插入图片描述

add.go中
在这里插入图片描述

update.go中
在这里插入图片描述

4、 结构体转化成map工具包

结构体转换成map工具类utils/utils.go

// Package utils 工具层
package utils

import "encoding/json"

func Struct2Map(s interface{}) (map[string]string, error) {
    j, _ := json.Marshal(s) //先将结构体转换成字节
    m := make(map[string]string)
    err := json.Unmarshal(j, &m) //将字节转化成map
    if err != nil {
        return nil, err
    }
    return m, nil
}

在这里插入图片描述

测试添加集群接口
看下绑定的结构体,来确定前端传的数据
在这里插入图片描述

在这里插入图片描述

postman请求
我们先不传kubeconfig,可以看到检测到集群不正常,无法添加
在这里插入图片描述

我们把集群的~/.kube/config填进去
在这里插入图片描述

注意,需要把换行符改成\n。不然postman无法传参
在这里插入图片描述

再次请求
在这里插入图片描述

k8s集群查看secret,添加成功
在这里插入图片描述

查看详情
在这里插入图片描述

更新集群
只需要在请求处,将URL改成update,然后修改字段即可
在这里插入图片描述

5、删除集群

删除集群,只需要根据集群ID,将对应的secret删除掉即可
删除集群的控制器deleteCluster.go

package cluster

import (
    "context"
    "github.com/gin-gonic/gin"
    "jingtian/krm-backend/config"
    "jingtian/krm-backend/utils/logs"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func DeleteCluster(c *gin.Context) {
    logs.Debug(nil, "删除集群")
    // 1.根据传来的id来删除secret
    clusterId := c.Query("id")
    // 2.删除secret
    err := config.InClusterClientSet.CoreV1().Secrets(config.MetadataNamespace).Delete(context.TODO(), clusterId, metav1.DeleteOptions{})
    // 定义响应格式
    returnData := config.NewReturnData()
    if err != nil {
        logs.Error(map[string]interface{}{"id": clusterId, "message": err.Error()}, "删除失败")
        // 说明删除集群失败
        msg := "集群删除失败: " + err.Error()
        returnData.Status = 400
        returnData.Msg = msg
    } else {
        logs.Warning(map[string]interface{}{"id": clusterId}, "删除成功")
        // 说明删除成功
        returnData.Status = 200
        returnData.Msg = "删除成功"
        delete(config.ClusterKubeconfig, clusterId)
    }
    //响应给前端
    c.JSON(200, returnData)
}

在这里插入图片描述

请求
在这里插入图片描述

k8s查看,删除成功
在这里插入图片描述

6、集群列表查询

集群列表查询,就是查询所有的secret列表,将secret列表返回给前端就可以了
我们先通过postman创建几个secret,模拟几个集群
在这里插入图片描述

查看secret
在这里插入图片描述

我们把secret都查询出来,但是我们不要把secret的所有信息都传给前端,因为可能会不安全,所以尽量不要把kubeconfig传给前端
我们只需要传annotations里面的数据就行了
在这里插入图片描述

因此,我们在查询到secret列表时,需要处理下
查询的时候,可以过滤

//根据指定标签,过滤出我们添加的集群
listOptions := metav1.ListOptions{
    LabelSelector: config.ClusterConfigSecretLabelKey + "=" + config.ClusterConfigSecretLabelValue,
}
secretList, err := config.InClusterClientSet.CoreV1().Secrets(config.MetadataNamespace).List(context.TODO(), listOptions)

ListOptions里面可以根据LabelSelector或者FieldSelector等过滤
在这里插入图片描述

查询集群列表list.go完整代码

package cluster

import (
    "context"
    "github.com/gin-gonic/gin"
    "jingtian/krm-backend/config"
    "jingtian/krm-backend/utils/logs"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func List(c *gin.Context) {
    logs.Debug(nil, "列出集群列表")
    //根据指定标签,过滤出我们添加的集群
    listOptions := metav1.ListOptions{
        LabelSelector: config.ClusterConfigSecretLabelKey + "=" + config.ClusterConfigSecretLabelValue,
    }
    secretList, err := config.InClusterClientSet.CoreV1().Secrets(config.MetadataNamespace).List(context.TODO(), listOptions)
    returnData := config.NewReturnData()
    if err != nil {
        logs.Info(map[string]interface{}{"message": err.Error()}, "查询集群列表失败")
        // 查询失败
        msg := "查询失败: " + err.Error()
        returnData.Status = 400
        returnData.Msg = msg
        c.JSON(200, returnData)
        return
    }
    // 优化数据返回的结构,将annotions里面的map以切片的形式传给前端
    var clusterList []map[string]string
    for _, v := range secretList.Items {
        anno := v.Annotations
        clusterList = append(clusterList, anno)
    }
    returnData.Msg = "查询成功"
    returnData.Data["items"] = clusterList
    c.JSON(200, returnData)

}

在这里插入图片描述

postman请求,拿到集群列表
在这里插入图片描述

7、获取集群详情

我们查询一个集群的配置的时候,需要用到get,还有就是编辑一个集群的时候,也需要用到查询集群的详情。
我们可以根据集群id来获取到secret,返回给前端

控制器get.go

package cluster

import (
    "context"
    "fmt"
    "github.com/gin-gonic/gin"
    "jingtian/krm-backend/config"
    "jingtian/krm-backend/utils/logs"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "net/http"
)

func Get(c *gin.Context) {
    logs.Debug(nil, "获取集群详情")
    // 1.根据传来的id来获取集群详情
    clusterId := c.Query("id")
    returnData := config.NewReturnData()
    // 2.获取集群的详细信息
    secretDetail, err := config.InClusterClientSet.CoreV1().Secrets(config.MetadataNamespace).Get(context.TODO(), clusterId, metav1.GetOptions{})
    if err != nil {
        logs.Error(map[string]interface{}{"id": clusterId, "message": err.Error()}, "获取集群信息失败")
        returnData.Status = 400
        returnData.Msg = "获取集群详情失败 " + err.Error()
    } else {
        returnData.Msg = "集群详情查询成功"
        fmt.Println("secretDetail是什么", secretDetail)
        clusterConfigMap := secretDetail.Annotations
        // Data map[string][]byte `json:"data,omitempty" protobuf:"bytes,2,rep,name=data"` Data 键是字符串,值是字节
        // 通过string将值转化成字符串
        //获取的时候不可以用StringData,因为secret里面没有这个字段。只有Data字段
        clusterConfigMap["kubeconfig"] = string(secretDetail.Data["kubeconfig"])
        returnData.Data["item"] = clusterConfigMap
    }
    c.JSON(http.StatusOK, returnData)

}

postman请求,查询到集群详情数据
在这里插入图片描述

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

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

相关文章

计算机网络——SDN

分布式控制路由 集中式控制路由

qt QTableWidgetItem详解

1、概述 QTableWidgetItem 是 Qt 框架中的一个类,专门用于在 QTableWidget(一个基于项的表格视图)中表示单个单元格的内容。QTableWidget 继承自 QAbstractItemView,而 QTableWidgetItem 则作为表格中的一个单元格项,…

DevExpress中文教程 - 如何使用AI模型检查HTML编辑中的语法?

DevExpress .NET MAUI多平台应用UI组件库提供了用于Android和iOS移动开发的高性能UI组件,该组件库包括数据网格、图表、调度程序、数据编辑器、CollectionView和选项卡组件等。 目前许多开发人员正在寻找多种方法将AI添加到解决方案中(这通常比想象的要…

vue中html如何转成pdf下载,pdf转base64,忽略某个元素渲染在pdf中,方法封装

一、下载 html2Canvas jspdf npm install jspdf html2canvas二、封装转换下载方法 htmlToPdf.js import html2Canvas from html2canvas import JsPDF from jspdf/*** param {*} reportName 下载时候的标题* param {*} isDownload 是否下载默认为下载,传false不…

day05(单片机)SPI+数码管

目录 SPI数码管 SPI通信 SPI总线介绍 字节交换原理 时序单元 ​​​​​​​SPI模式 模式0 模式1 模式2 模式3 数码管 介绍 74HC595芯片分析 ​​​​​​​原理图分析 ​​​​​​​cubeMX配置​​​​​​​ 程序编写 硬件SPI ​​​​​​​软件SPI 作业: SPI数…

越学越爽!4小时从零入门大模型教程,2024最详细的学习路线,让你少走99%弯路!(大模型/LLM/Agent/提示工程)

第一阶段:基础理论入门 目标:了解大模型的基本概念和背景。 内容: 人工智能演进与大模型兴起。 大模型定义及通用人工智能定义。 GPT模型的发展历程。 第二阶段:核心技术解析 目标:深入学习大模型的关键技术和工…

论文速读:简化目标检测的无源域适应-有效的自我训练策略和性能洞察(ECCV2024)

中文标题:简化目标检测的无源域适应:有效的自我训练策略和性能洞察 原文标题:Simplifying Source-Free Domain Adaptation for Object Detection: Effective Self-Training Strategies and Performance Insights 此篇文章为论文速读&#xff…

小白入门学习计算机辅助工具--Git和Github

虽然平时大家都有听过Github,但这实际上要分为Git和Github,我们可以简单理解为前者是用于本地,后者是远程端。下面我们来看看一些基本的操作。 Github创建仓库 让我们先从Github开始,点击右边的绿色按钮new进入创建库界面&#x…

【C++】哈希表封装 unordered_map 和 unordered_set 的实现过程

C语法相关知识点可以通过点击以下链接进行学习一起加油!命名空间缺省参数与函数重载C相关特性类和对象-上篇类和对象-中篇类和对象-下篇日期类C/C内存管理模板初阶String使用String模拟实现Vector使用及其模拟实现List使用及其模拟实现容器适配器Stack与QueuePriori…

攀拓(PAT)- 程序设计(乙级)2024年春季考试

题目来源:https://pintia.cn/market/item/1767454903977603072 B-1 题目要求 2024 这个数字,可以由 n n n个互不相同的正偶数和 m m m个互不相同的正奇数组合出来吗?本题就请你回答这个问题。 输入格式: 输入在一行中给出一个…

STM32 BootLoader 刷新项目 (九) 跳转指定地址-命令0x55

STM32 BootLoader 刷新项目 (九) 跳转指定地址-命令0x55 前面我们讲述了几种BootLoader中的命令,包括获取软件版本号、获取帮助、获取芯片ID、读取Flash保护Level。 下面我们来介绍一下BootLoader中最重要的功能之一—跳转!就像BootLoader词汇中的Boot…

VTK知识学习(2)-环境搭建

1、c方案 1.1下载源码编译 官网获取源码。 利用Cmake进行项目构建。 里面要根据实际使用的情况配置相关的模块哟,这个得你自行研究下了。 CMAKEINSTALLPREFIX--这个选项的值表示VTK的安装路径,默认的路径是C:/Program Files/VTK。该选项的值可不作更…

Chrome(谷歌浏览器中文版)下载安装(Windows 11)

目录 Chrome_10_30工具下载安装 Chrome_10_30 工具 系统:Windows 11 下载 官网:https://chrome.google-zh.com/,点击立即下载 下载完成(已经下过一遍所以点了取消) 安装 解压,打开安装包 点击下一步…

如何在算家云搭建Aatrox-Bert-VITS2(音频生成)

一、模型介绍 ‌ Aatrox - Bert -VITS2 模型是一种基于深度学习的语音合成系统,结合了 BERT 的预训练能力和 VITS2 的微调技术,旨在实现高质量的个性化语音合成。 二、模型搭建流程 1. 创建容器实例 进入算家云的“应用社区”,点击搜索找到…

232转485模块测试

概述 常用的PLC一般会有两个左右的232口,以及两个左右的485口,CAN口等,但是PLC一般控制的设备可能会有很多,会超出通讯口的数量,此时我们一般会采用一个口接多个设备,这种情况下要注意干扰等因素&#xff0…

静态数组类型无法用“=“给整个静态数组赋值

基础知识: 什么是静态数组类型? 在 C 中,静态数组是一种在编译时大小固定的数组。这意味着一旦声明,静态数组的大小就不能改变。 ------ 你可以声明一个静态数组并选择性地初始化它: int arr[10]; // 声明一个包…

使用 Python 调用云 API 实现批量共享自定义镜像

本文介绍如何通过 Python SDK 调用 API 接口,通过子用户批量共享云服务器自定义镜像。若您具备类似需求,或想了解如何使用 SDK,可参考本文进行操作。 前提条件 已创建子用户,并已具备云服务器及云 API 所有权限。 创建子用户请…

【旷视科技-注册/登录安全分析报告】

前言 由于网站注册入口容易被黑客攻击,存在如下安全问题: 暴力破解密码,造成用户信息泄露短信盗刷的安全问题,影响业务及导致用户投诉带来经济损失,尤其是后付费客户,风险巨大,造成亏损无底洞…

GISBox VS ArcGIS:分别适用于大型和小型项目的两款GIS软件

在现代地理信息系统(GIS)领域,有许多大家耳熟能详的GIS软件。它们各自具有独特的优势,适用于不同的行业需求和使用场景。在众多企业和开发者面前,如何选择合适的 GIS 软件成为了一个值得深入思考的问题。今天&#xff…

【Spring】Spring Web MVC基础入门~(含大量例子)

阿华代码,不是逆风,就是我疯 你们的点赞收藏是我前进最大的动力!! 希望本文内容能够帮助到你!! 目录 一:什么是Spring Web MVC 1:Servlet 2:总结 二:MVC …