后端笔记之gin框架学习

gin框架学习

    • 1. 使用脚手架搭建gin框架
    • 2. 应用框架
    • 3. 路由管理
    • 4.自定义中间件的使用
    • 5. 通过中间件设置路由权限校验
      • 1. 自定义校验
      • 2. 配置跨域
      • 3. 使用jwt进行tokn校验
    • 6. 接口入参获取和绑定
      • 2. 参数校验
      • 3. protobuf
    • 7. 集成mysql数据库
      • 1. gorm使用

1. 使用脚手架搭建gin框架

gin框架推荐使用 go mod 来管理依赖,所以使用 go mod 获取 gin

在 $GOPATH/src 下新建一个 gin(项目包名,自定义)文件夹,进入其中后,执行命令: go get -u github.com/gin-gonic/gin 即可自动下载依赖,等到依赖下载完成之后,在当前目录执行 go mod tidy 即可检查依赖完整性。

至此一个 gin 框架就搭建完成了。

2. 应用框架

在 gin 文件夹下建立一个 main.go 文件,编写 main 函数引入 gin 引擎。

        package mainimport ("github.com/gin-gonic/gin")func main() {// 获取 gin 引擎ginEngine := gin.Default()// 设置一个 get 路由,以及 handle 方式ginEngine.GET("/ping", func(c *gin.Context) {c.JSON(200, gin.H{"message": "pong",})})// 运行 gin 进程, 默认 8080 端口ginEngine.Run()}

至此,这个框架已经可以启动了。

3. 路由管理

在 gin 目录下新建一个包,包名通常为 router

router 下的一个 go 文件通常代表一个路由分组,例如下面是个无权限白名单的接口分组示例:

新建 public.go 文件

        package routerimport ("gin/web""github.com/gin-gonic/gin")func InitPublicApi(ginEngine *gin.Engine) {// 分组, ginEngine的Group是设置根目录 apiapi := ginEngine.Group("/api")// 设置次级目录 v1v1 := api.Group("/v1")// 设置 v1 下的子路径,假使这个路径是获取验证码,第二个参数就是具体的处理方式v1.GET("/verificationCode", web.VerificationCode)// 设置一个登录的 POST 路径v1.POST("/login", web.Login)}

同时 gin 目录下新建 web 文件夹,内部搭配新建 public.go 文件,然后用于处理路由指引过来的业务逻辑

        package webimport ("fmt""github.com/gin-gonic/gin")func VerificationCode(context *gin.Context) {fmt.Println("生成了一个验证码")}func Login(context *gin.Context) {fmt.Println("处理了登录逻辑")}

在 router 下新建 routers.go 文件,在这里把所有的路由都创建出来

        package routerimport "github.com/gin-gonic/gin"func InitRouters(ginEngine *gin.Engine) {InitPublicApi(ginEngine)InitUserApi(ginEngine)}

然后在 main 函数中调用即可

        package mainimport ("github.com/gin-gonic/gin")func main() {// 获取 gin 引擎ginEngine := gin.Default()// 创建所有的路由router.InitRouters(ginEngine)// 运行 gin 进程, 默认 8080 端口,但是可以修改为任意的空闲端口,以下改变为了8081ginEngine.Run(":8081")}

4.自定义中间件的使用

在gin框架中,我们所有要对公共的处理都可以使用中间件来实现,所谓的中间件就是通过函数作为参数在完成本函数之前去额外做一些操作,由于。

首先现在gin目录下新建middleware文件夹,然后新建 myHandler.go 文件。

        package middlewareimport ("fmt""github.com/gin-gonic/gin")// MyHandler 这边自定义一个中间件,打印请求路径和方式func MyHandler() gin.HandlerFunc {// 返回一个匿名函数return func(context *gin.Context) {path := context.FullPath()       // 获取完整的请求连接method := context.Request.Method // 获取请求方式类型fmt.Printf("myHandler, path:%s, method: %s\n", path, method)context.Next()}}

然后再 main 函数中引入使用

       func main() {// 获取 gin 引擎ginEngine := gin.Default()// 中间件使用,用ginEngine.useginEngine.Use(middleware.MyHandler())// 创建整个路由router.InitRouters(ginEngine)// 运行 gin 进程, 默认 8080 端口, 可以改变为任意空闲端口ginEngine.Run(":8081")}

当我们再次请求后台时,路径和方式就被打印出来了
在这里插入图片描述

5. 通过中间件设置路由权限校验

1. 自定义校验

在 middleware 文件夹下新建 token.go 文件,用来作为校验 token 的中间件

        package middlewareimport ("errors""github.com/gin-gonic/gin""net/http")var token = "123456"func TokenCheck(context *gin.Context) {// 从请求头中获取tokenaccessToken := context.GetHeader("access_token")// 进行token校验,在正规流程中,这里应该拿着token做完加解密之后,通过jwt获取存于缓存或者redis中的用户信息,并把用户信息写入请求之中// 这里模拟上述操作,仅仅是比较一下token后,塞入用户信息if accessToken != token {context.JSON(http.StatusInternalServerError, gin.H{"message": "token 校验失败",})// 防止此处理继续下去// context.Abort()// 防止此处理继续下去并报错context.AbortWithError(http.StatusInternalServerError, errors.New("token checker fail"))}// 如果校验成功之后,那么将要通过jwt根绝token获取到用户信息,加入请求中context.Set("userId", "123456789")context.Set("userName", "sue")context.Next()}

然后再需要使用 token 校验的路由上配置

        package routerimport ("fmt""gin/middleware""github.com/gin-gonic/gin")func InitUserApi(ginEngine *gin.Engine) {// 在这里配置上需要校验权限的路由的中间件user := ginEngine.Group("/user", middleware.TokenCheck)v1 := user.Group("/v1")//路径传参v1.GET("/detail/:id", func(context *gin.Context) {// 可以通过 Param 获取到参数值id := context.Param("id")// 在这里获取中间件中塞入的信息userId = context.getString("userId")fmt.Println(userId)context.String(200, "ID is %s", id)})}

2. 配置跨域

使用 go get -u github.com/gin-contrib/cors下载跨域依赖包,然后编写中间件

    func Cors() gin.HandlerFunc {return cors.New{AllowAllOrigins: true,AllowHeaders: []string{"Origin", "Content-Length", "Content-Type",},AllowMethods: []string{"GET", "POST", "DELETE", "PUT", "HEAD", "OPTIONS"}}}

然后再 router 最根部使用

     	package routerimport ("github.com/gin-gonic/gin""gin/middleware"func InitRouters(ginEngine *gin.Engine) {api := ginEngine.Group("/api")api.Use(middleware.Cors())InitPublicApi(api)InitUserApi(api)}

3. 使用jwt进行tokn校验

先要去下载 jwt 依赖,使用go get -u github.com/golang-jwt/jwt/v5下载。

之后中间件包下,新建 jwt.go

        package middlewareimport "github.com/golang-jwt/jwt/v5"// 秘钥var key = "abcdefg123456"type Data struct {Id                   stringName                 stringAge                  intSex                  intjwt.RegisteredClaims // 这里就是使用鸭子模式,自动隐式判断继承}// Sign 签发tokenfunc Sign(data jwt.Claims) (string, error) {// 选择加密方式签发tokentoken := jwt.NewWithClaims(jwt.SigningMethodHS256, data)// 签名转化为字符串sign, err := token.SignedString([]byte(key))if err != nil {return "", err}return sign, err}// Verify 验签func Verify(sign string, data jwt.Claims) error {_, err := jwt.ParseWithClaims(sign, data, func(token *jwt.Token) (interface{}, error) {return []byte(key), nil})return err}

然后修改原来的token验证方法

       package middlewareimport ("errors""github.com/gin-gonic/gin""net/http")func TokenCheck(context *gin.Context) {// 从请求头中获取tokenaccessToken := context.GetHeader("access_token")// 进行token校验,在正规流程中,这里应该拿着token做完加解密之后,通过jwt获取存于缓存或者redis中的用户信息,并把用户信息写入请求之中data := &Data{}// 调用验签方法err := Verify(accessToken, data)if err != nil {context.JSON(http.StatusInternalServerError, gin.H{"message": "token 校验失败",})// 防止此处理继续下去// context.Abort()// 防止此处理继续下去并报错context.AbortWithError(http.StatusInternalServerError, errors.New("token checker fail"))}// 把用户信息塞到这次请求的上下文中context.Set("user", data)context.Next()}

之后,修改登录方法

        func Login(context *gin.Context) {req := &loginReq{}err := context.ShouldBindJSON(req)if err != nil {context.JSON(http.StatusInternalServerError, gin.H{"message": err.Error(),})return}// 在这里应该通过name 和 password 去数据库取到用户信息,然后填充进签名信息中data := middleware.Data{Id:   "123",Name: req.Name,Sex:  1,Age:  18,RegisteredClaims: jwt.RegisteredClaims{// 有效期为一个小时ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),// 签发时间IssuedAt: jwt.NewNumericDate(time.Now()),// 有效期的开始时间NotBefore: jwt.NewNumericDate(time.Now()),},}sign, err := middleware.Sign(data)if err != nil {context.JSON(http.StatusInternalServerError, gin.H{"message": err,})return}context.JSON(http.StatusOK, gin.H{"token": sign,})}

最后,先调用登录信息拿到 token 之后放入请求头的 access_token 中,调用获取当前登录人的信息的接口,代码如下:

  	v1.GET("/detail", func(context *gin.Context) {// 从上下文中获取在token中间件中塞入的用户信息if userData, exist := context.Get("user"); exist {// 返回给前端context.JSON(http.StatusOK, userData)return}context.JSON(http.StatusInternalServerError, gin.H{"message": "用户不存在",})})

6. 接口入参获取和绑定

  1. 入参获取

  2. 在restful风格接口里面传参,比如像/xxx/xxx/500,在代码中路由地址中声明为”/xxx/xxx/:id“,那么可以使用Param获取

       v1.GET("/detail/:id", func(context *gin.Context) {// 可以通过 Param 获取到参数值id := context.Param("id")context.String(200, "ID is %s", id)})
  1. 在传统的url传参的形式下,如/xxx/xxx?id=500,那么可以使用Query获取
      	v1.GET("/detail?id=500", func(context *gin.Context) {// 可以通过 Param 获取到参数值id := context.Query("id")context.String(200, "ID is %s", id)})
  1. 在 form 表单提交的时候,使用结构体去承接入参
    在 web 包中的逻辑处理代码如下:
 			type loginReq struct {name     string `form:"nickName" json:"nickName"` // 反括号内中 form是表名提交方式, nickName是表名提交的数据的keypassword string `form:"password" json:"password"` // 那么对应的json提交的时候,这里就是 `json:"password"`,前面的属性也应该首字母大写,JSON会自动转化为小写}func Login(context *gin.Context) {// 声明承接的结构体实例req := &loginReq{}// shouldBind 为绑定提交,基本上可以应对所有的方式提交,json提交时也可以指明为使用ShouldBindJSON// Bind 和 ShouldBind区别在于使用 Bind 当入参不满足格式时会返回400错误,而不是继续向下执行进入代码中设定的错误。err := context.ShouldBind(req)if err != nil {context.JSON(http.StatusInternalServerError, gin.H{"message": err.Error(),})return}fmt.Println(req)context.JSON(http.StatusOK, req)}

请求和返回如下图所示:
在这里插入图片描述

2. 参数校验

验证框架是:validator

几个校验示例:

    type loginReq struct {Name     string `json:"nickName" binding: "required"` // 加入binding: "required"是验证必填Password string `json:"password"`Phone    string `json:"password binding: "required,el64"` // el64是验证电话格式Email    string `json:"password binding: "omitempty,email"` // omitempty为当前值为空,就不在进行后续校验,email是验证邮箱格式}

3. protobuf

Protobuf(Protocol Buffers)是一种由 Google 开发的数据序列化格式和编程语言无关的接口定义语言(IDL,Interface Definition Language)。它的主要目的是用于定义数据结构和消息格式,以便在不同平台和不同语言之间进行高效的数据交换。

使用 Protobuf,你可以定义结构化数据的消息格式,并生成针对多种编程语言的序列化和反序列化代码。这使得不同系统之间可以相互通信、交换数据,而无需关心底层的数据表示和传输细节。

Protobuf 提供了一种简洁、高效的二进制编码格式,它比 XML 和 JSON 等文本格式更紧凑,解析速度更快,同时也更容易进行版本升级和兼容性处理。因此,Protobuf 在诸如分布式系统通信、数据存储和数据交换等领域具有广泛的应用。

使用 Protobuf 的基本流程如下:

  1. 使用 Protobuf 的语法定义消息类型和结构化数据的字段。
  2. 使用 Protobuf 编译器将定义的 Protobuf 文件(通常以 .proto 为后缀)转换为目标语言的代码文件。
  3. 在程序中使用生成的代码来序列化和反序列化消息。

总结起来,Protobuf 提供了一种跨语言、跨平台的数据序列化方案,使得不同系统之间可以高效、可靠地进行数据交换和通信。

7. 集成mysql数据库

1. gorm使用

gorm 是 golang 对于 sql 和对象的一种关系映射的框架,orm因为牵扯到映射,所以不推荐用于多表连接的操作。

使用go get -u gorm.io/gorm 下载依赖,以及go get -u gorm.io/driver/mysql 下mysql驱动。文档地址

  1. 创建连接池
    然后在 middleware 文件夹下面新建 grom.go 文件
 	   package middlewareimport ("gorm.io/driver/mysql""gorm.io/gorm""gorm.io/gorm/logger""log""time")var DB *gorm.DBvar dsn = "admin:pass12345sx@tcp(127.0.0.1:3306)/hit-plum?charset=utf8mb4&parseTime=True&loc=Local"func init() {var err errorDB, err = gorm.Open(mysql.New(mysql.Config{DSN:                       dsn,   // data source nameDefaultStringSize:         256,   // 默认字符配置DisableDatetimePrecision:  true,  // 禁用日期时间精度,MySQL 5.6之前不支持此功能DontSupportRenameIndex:    true,  // 重命名索引时删除并创建,MySQL 5.7之前不支持重命名索引,MariaDBDontSupportRenameColumn:   true,  // `change`重命名列时,MySQL 8、MariaDB之前不支持重命名列SkipInitializeWithVersion: false, // 基于当前MySQL版本自动配置}), &gorm.Config{Logger: logger.Default.LogMode(logger.Info), // 设置一下log的日志级别})if err != nil {log.Println(err)return}setPool(DB)}// setPool 设置连接池func setPool(db *gorm.DB) {sqlDB, err := db.DB()if err != nil {log.Println(err)return}sqlDB.SetMaxIdleConns(5)                // 最大空闲连接数,即最大可以长期保持的连接数,超过这个数的链接会按照策略自动关闭sqlDB.SetMaxIdleConns(10)               // 最大同时连接数sqlDB.SetConnMaxLifetime(time.Hour / 2) // 设置最大存活周期为半小时,超过这个周期还存在链接将会被回收}
  1. 新增model
    在 web 包下新建 models 包,新增 user.go 文件
       package modelsimport ("time""gorm.io/gorm")// 共有的字段type Model struct {gorm.Model // gorm 提供的共有字段UpdatedBy stringCreatedBy stringDeletedBy string}type User struct {Model     Model  `gorm:"embedded"` // 嵌入共有的一些字段UserName  string `gorm:"column:user_name"` // 声明对应的列名Password  stringRealName  stringUserType  uintEmail     stringPhone     stringDepartId  intGender    uintAvatar    string `gorm:"type:text"` // 声明对应的字段数据库数据类型Enabled   uintDelFlag   uintLoginIp   stringLoginDate time.TimeRemark    string `gorm:"type:text"`}// TableName 方法指定表名, 默认是根据 结构体名字后加个s为表名,不符合此规范就要单独声明表名func (User) TableName() string {return "user"}
  1. 增删改查
    web 下新增一个 dao 包,然后建立一个 user.go 文件
// 用于测试的数据对象var userTempData = models.User{UserName:  "sue",Password:  "123456",RealName:  "ElvisSue",UserType:  1,Email:     "ElvisSue@163.com",Phone:     "15066514789",DepartId:  456,Gender:    1,Avatar:    "",Enabled:   0,DelFlag:   1,LoginIp:   "localhost",LoginDate: time.Now(),Remark:    "this is a handsome man",Model: models.Model{CreatedBy: "admin",UpdatedBy: "",DeletedBy: "",},}

增:

       // CreateUser 新增一条数据func CreateUser() {t := userTempDatares := middleware.DB.Create(&t)fmt.Println(res.RowsAffected)fmt.Println(res.Error)fmt.Println(t)}// PartialCreateUser 部分插入func PartialCreateUser() {t := userTempDatares := middleware.DB.Select("UserName", "Password", "CreatedBy").Create(&t)fmt.Println(res.RowsAffected)fmt.Println(res.Error)fmt.Println(t)}// OmitCreateUser 忽略某些字段插入func OmitCreateUser() {t := userTempDatares := middleware.DB.Omit("LoginIp").Create(&t)fmt.Println(res.RowsAffected)fmt.Println(res.Error)fmt.Println(t)}// BatchCreateUser 批量插入func BatchCreateUser() {users := make([]models.User, 8)for i := 0; i < 3; i++ {t := userTempDatausers = append(users, t)}res := middleware.DB.Create(&users)fmt.Println(res.RowsAffected)fmt.Println(res.Error)}

删:其实现在大部分数据采用的是逻辑删除,删这个操作并不是特别重要,所以一个接口演示出所有的情况

       // DeleteUser 删除用户func DeleteUser(){temp := models.User{id: 10}middleware.DB.Delete(&temp) // 删除主键为10的middleware.DB.Delete(&models.User{}, 10) // 删除主键为10的middleware.DB.Delete(&models.User{}, []int{1,2,3})// 删除主键 in [1, 2, 3]的middleware.DB.Where("user_name = ?", "jinzhu").Delete(&temp) // 删除主键为10的且 user_name 为 “jinzhu” 的middleware.DB.Delete(&models.User{},"user_name LIKE ?", "%jinzhu%") // 删除主键为10的且 user_name 包含 “jinzhu” 的}

改:
// Save 这个接口有些特殊,在未声明where条件且要传入的model不含有主键的情况下,会默认使用 insert 去更新表数据,设置 where 条件后或者 model 中有已存在的主键才会使用 update 更新表数据

        var temp = models.User{ID: 8UserName: "lee",Password: "153654"}// SaveUser 修改一条数据func SaveUser() {t := tempmiddleware.DB.Save(&t)// 因为ID是主键,更改了 ID 为8的数据}// SaveSingleColumn 修改单一列func SaveSingleColumn (){t := temp// 使用空的结构体来声明表名进行操作middleware.DB.Model(&models.User{}).Where("enabled = ?", 1).Update("UserName", "hello")// 使用已有主键的model进行操作, 即 where id = xmiddleware.DB.Model(&userTempData).Update("UserName", "hello")// 使用已有主键的model进行操作加where, 即 where id = x and enabled = 1middleware.DB.Model(&userTempData).Where("enabled = ?", 1).Update("UserName", "hello")}// SaveMultipleColumn 多列更改func SaveMultipleColumn (){t := temp// 当满足 where id = 5 and enabled = 1 的时候,t 中有的属性都会更改middleware.DB.Model(&models.User{ID: 5}).Where("Enabled = ?", 1).Updates(&t)}// SaveSelectUser 选择列修改,其实和多列修改差不多func SaveSelectUser(){// 满足 where id = 5 只修改 user_name 的值middleware.DB.Model(&models.User{ID: 5}).Select("UserName").Updates(&t)}

查:

       func QueryUser() {var user models.Uservar users []models.Usermiddleware.DB.Model(&models.User{}).First(&user) // 按照id排序去第一条middleware.DB.Model(&models.User{}).Take(&user) // 默认第一条middleware.DB.Model(&models.User{}).Last(&user) // id 排序最后一条middleware.DB.Model(&models.User{}).First(&user, 8) // id = 8的middleware.DB.Model(&models.User{}).First(&user,"id = ?",  8) // id = 8的middleware.DB.Model(&models.User{}).First(&user, []int{1, 2, 3})// id in [1,2,3]的middleware.DB.Where("user_name <> ?", "jinzhu").Find(&users) // 所有 user_name <> 'jinzhu'}

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

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

相关文章

MapReduce和Yarn部署+入门

看的黑马视频记的笔记 目录 1.入门知识点 分布式计算&#xff1a; 概念&#xff1a; 两种模式&#xff1a; MapReduce&#xff08;分布式计算&#xff0c;分散汇总模式&#xff09; 概念 执行原理 注&#xff1a; Yarn&#xff08;分布式资源调度&#xff09; 概述 Y…

VS Code配置Go语言开发环境

提示&#xff1a;首先这是一个新型语言&#xff0c;最好把vscode更新到最新版。 1&#xff1a;去官网下载Go语言编译器&#xff0c;之后配置到系统环境中&#xff0c;能看到版本就行。 2&#xff1a;创建一个文件夹&#xff0c;存放go的工具文件&#xff0c;我的在D:\GoFile\G…

【数据分析之Numpy】Numpy中复制函数numpy.repeat()与numpy.tile()的使用方法及区别

一、简介 numpy.repeat()与numpy.tile()都是Numpy库中的复制函数&#xff0c;用于将数组中的元素重复指定的次数。 numpy.repeat()函数接受三个参数&#xff1a;要重复的数组、重复的次数和重复的轴。 numpy.tile()函数接受两个参数&#xff1a;要重复的数组和重复的次数。 二…

elasticsearch简单相关操作

查看索引 GET _cat/indices //获取所有的index GET account发送post不带id新建数据 POST user/_doc/ {"name":"bobby","compamy":"imooc" }如果post带id就和put一样的操作了&#xff0c; put是不允许不带id的 post _create 没有就…

【前端】vscode 相关插件

一 插件&#xff1a; 01、ESLint 用来识别并检查ECMAScript/JavaScript 代码的工具 02、Prettier 用来格式化代码&#xff0c;如.js、.vue、css等都可以进行格式化 03、Vetur 用来识别并高亮vue语法 04、EditorConfig 用来设置vscode的编程行为 二、安装依赖 01、…

干涉光学测试导论

1.用于光学测试的基本干涉仪 2。相移干涉术 3。专业光学测试 4。长波长干涉术 5。非球面试验 6。表面微观结构的测量 7。绝对测量 8。结束语 第1部分-光学测试用基本干涉仪 (1)双光束干涉 (2)菲佐干涉仪和特维曼-格林干涉仪 (3)测试平面和球面的基本技术 (4)球面的基本…

maui中实现加载更多 RefreshView跟ListView(2)

一个类似商品例表的下拉效果&#xff1a; 代码 新增个类为商品商体类 public class ProductItem{public string ImageSource { get; set; }public string ProductName { get; set; }public string Price { get; set; }}界面代码&#xff1a; <?xml version"1.0&quo…

通过费用流中的贪心来保证计数正确性:P4249剪刀石头布

https://vj.imken.moe/contest/598718#problem/K 三元环数量尽量多&#xff0c;也就是非三元环数量尽可能少。非三元环的充要条件是存在一个点度数为2&#xff0c;而每条边可以给一个点一个度数&#xff0c;然后就变成了经典网络流问题。 但是&#xff0c;对于一个点&#xf…

计算机与自动医疗检查仓:技术革新引领医疗未来

计算机与自动医疗检查仓&#xff1a;技术革新引领医疗未来 一、引言 随着科技的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;和机器学习&#xff08;ML&#xff09;已经成为现代社会不可或缺的一部分。它们的应用领域日益扩展&#xff0c;从简单的日常任务到复杂…

数据结构--图

树具有灵活性&#xff0c;并且存在许多不同的树的应用&#xff0c;但是就树本身而言有一定的局限性&#xff0c;树只能表示层次关系&#xff0c;比如父子关系。而其他的比如兄弟关系只能够间接表示。 推广--- 图 图形结构中&#xff0c;数据元素之间的关系是任意的。 一、图…

基于ssm的航班订票管理系统论文

摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。针对航班订票信息管理混乱&#xff0c;出错率高&#xff0c;信息安全性差…

【PID学习笔记10】PID公式分析

写在前面 前面已经将控制系统的基础知识点过了一遍&#xff0c;从本节开始&#xff0c;将正式学习PID控制的相关知识&#xff0c;将会从基本的PID公式概念解释&#xff0c;再基于matlab仿真介绍十几种数字式PID的基本概念。本文重点讲解PID的经典公式。 一、连续与离散的概念…

基于PaddleOCR一键搭建文字识别和身份证识别web api接口

前言 通过这篇文章【基于PaddleOCR的DBNet神经网络实现全网最快最准的身份证识别模型】开发的身份证识别模型&#xff0c;还无法进行部署应用&#xff0c;这篇文章就已经开发好的代码如何部署&#xff0c;并如何通过api的接口进行访问进行讲解。 项目部署 以windows系统为例&…

Python 爬虫之简单的爬虫(三)

爬取动态网页&#xff08;上&#xff09; 文章目录 爬取动态网页&#xff08;上&#xff09;前言一、大致内容二、基本思路三、代码编写1.引入库2.加载网页数据3.获取指定数据 总结 前言 之前的两篇写的是爬取静态网页的内容&#xff0c;比较简单。接下来呢给大家讲一下如何去…

20V升26V 600mA升压型LED驱动芯片,PWM调光芯片-AH1160

AH1160是一个功能强大的升压型LED驱动芯片&#xff0c;专为需要精确控制LED亮度的PWM调光应用而设计。它可将20V输入电压升压至26V&#xff0c;同时提供稳定的600mA电流输出&#xff0c;适用于各种LED照明设备。 芯片特点&#xff1a; 1. 输入电压范围&#xff1a;AH1160可在…

linux驱动的学习 驱动开发初识

1 设备的概念 在学习驱动和其开发之前&#xff0c;首先要知道所谓驱动&#xff0c;其对象就是设备。 1.1 主设备号&次设备号&#xff1a; 在Linux中&#xff0c;各种设备都以文件的形式存在/dev目录下&#xff0c;称为设备文件。最上层的应用程序可以打开&#xff0c;关…

uniapp获取键盘高度顶起底部输入框

核心代码&#xff1a; uni.onKeyboardHeightChange((res) > {console.log(res.height);//转化为rpxthis.KeyHight res.height;}); 全部代码&#xff1a; <template><view class"pagesone" :class"bg-themeColor.name" style"padding-t…

【Go】基于GoFiber从零开始搭建一个GoWeb后台管理系统(五)角色管理、菜单管理模块

窝来辣&#x1f601; 下面是前几篇的内容&#xff1a; 第一篇&#xff1a;【Go】基于GoFiber从零开始搭建一个GoWeb后台管理系统&#xff08;一&#xff09;搭建项目 第二篇&#xff1a;【Go】基于GoFiber从零开始搭建一个GoWeb后台管理系统&#xff08;二&#xff09;日志…

挑战52天学小猪佩奇笔记--day26

52天学完小猪佩奇--day26 ​【本文说明】 本文内容来源于对B站UP 脑洞部长 的系列视频 挑战52天背完小猪佩奇----day26 的视频内容总结&#xff0c;方便复习。强烈建议大家去关注一波UP&#xff0c;配合UP视频学习。 day26的主题&#xff1a;堆雪人 猜台词&#xff1a; 旁白&am…

卷积神经网络的学习与实现

基于matlab的卷积神经网络(CNN)讲解及代码_matlab中如何查看cnn损失函数-CSDN博客 可以看到与BP神经网络相比&#xff0c;卷积神经网络更加的复杂&#xff0c;这里将会以cnn作为学习案例。 1.经典反向传播算法公式详细推导 这里引用经典反向传播算法公式详细推导_反向目标公…