Gin框架使用

news2024/9/24 7:17:17

Gin

Gin框架安装与使用

下载安装Gin:

go get -u github.com/gin-gonic/gin

使用示例:

package main

import "github.com/gin-gonic/gin"

func main() {
	//创建一个默认的路由引擎
	r := gin.Default()
	//GET:请求方式,/hello:请求路径
	//当客户端以GET方法请求/hello路径时,会执行后面的匿名函数
	r.GET("/hello", func(c *gin.Context) {
		// c.JSON:返回JSON格式的数据
		c.JSON(200, gin.H{
			"message": "hello gin",
		})
	})
	//启动服务,默认端口是8080
	r.Run(":9090")
}

启动服务,浏览器访问:

image-20230313160947946

RESTful API

REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”或“表现层状态转化”。

简单来说,REST的含义就是客户端与Web服务器之间进行交互的时候,使用HTTP协议中的4个请求方法代表不同的动作。

  • GET用来获取资源
  • POST用来新建资源
  • PUT用来更新资源
  • DELETE用来删除资源。

image-20230313161046469

Gin框架支持开发RESTful API的开发。

package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

func main() {
	// 创建默认引擎
	r := gin.Default()
	//请求编写
	//查询
	r.GET("/book", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "GET",
		})
	})
	//创建
	r.POST("/book", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "POST",
		})
	})
	//修改
	r.PUT("/book", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message": "PUT",
		})
	})
	//删除
	r.DELETE("/book", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "DELETE",
		})
	})
	//启动HTTP服务
	r.Run()
}

模板引擎标准库 http/template

Go语言的模板引擎

Go语言内置了文本模板引擎text/template和用于HTML文档的html/template。它们的作用机制可以简单归纳如下:

  1. 模板文件通常定义为.tmpl.tpl为后缀(也可以使用其他的后缀),必须使用UTF8编码。
  2. 模板文件中使用{{}}包裹和标识需要传入的数据。
  3. 传给模板这样的数据就可以通过点号(.)来访问,如果数据是复杂类型的数据,可以通过{ { .FieldName }}来访问它的字段。
  4. {{}}包裹的内容外,其他内容均不做修改原样输出。

模板引擎的使用

Go语言模板引擎的使用可以分为三部分:定义模板文件、解析模板文件和模板渲染.

1、定义模板文件

2、解析模板文件

上面定义好了模板文件之后,可以使用下面的常用方法去解析模板文件,得到模板对象:

func (t *Template) Parse(src string) (*Template, error)
func ParseFiles(filenames ...string) (*Template, error)
func ParseGlob(pattern string) (*Template, error)

也可以使用func New(name string) *Template函数创建一个名为name的模板,然后对其调用上面的方法去解析模板字符串或模板文件。

3、模板渲染

渲染模板简单来说就是使用数据去填充模板,当然实际上可能会复杂很多。

func (t *Template) Execute(wr io.Writer, data interface{}) error
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error

JSON渲染

package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

func main() {
	r := gin.Default()
	r.GET("json", func(c *gin.Context) {
		// 方法一:使用map
		//data := map[string]interface{}{
		//	"name":    "Weiyuxin",
		//	"message": "hello gin",
		//	"age":     18,
		//}
		// gin.H预定义成map[string]interface{}
		data := gin.H{
			"name":    "Weiyuxin",
			"message": "hello gin",
			"age":     18,
		}

		c.JSON(http.StatusOK, data)
	})
	r.GET("/json1", func(c *gin.Context) {
		//方法2:结构体
		type msg struct {
			Name    string `json:"name"` //不能小写,否则无法访问,要想返回的json是小写,需要设置
			Message string
			Age     int8
		}
		data := msg{"weiyuexin", "hello gin json", 90}
		c.JSON(http.StatusOK, data) //json序列化使用反射
	})
	r.Run(":9090")
}

获取参数

获取querystring参数(常用于GET请求)

querystring指的是URL中?后面携带的参数,例如:`/user/search?username=小王子&address=沙河

package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

//获取参数
func main() {
	r := gin.Default()
	//GET请求URL 的?后面的是query string参数
	// 通过key-value格式,多个key-value通过 & 连接
	r.GET("/query", func(c *gin.Context) {
		//获取浏览器发送过来的请求携带的 query string
		name := c.Query("name") //通过Query获取请求中携带的querystring参数
		age := c.Query("age")   //通过Query获取请求中携带的querystring参数
		//name := c.DefaultQuery("name", "wyx") //查到的话就用查到的值,查不到就用默认值
		//name, ok := c.GetQuery("name") //返回取到的值和是否取到
		//if !ok {
		//	c.JSON(http.StatusOK, gin.H{
		//		"ok": "请输入参数",
		//	})
		//	return
		//}
		c.JSON(http.StatusOK, gin.H{
			"ok": "hello," + name + ",age=" + age,
		})
	})
	r.Run(":9090")
}

运行结果:

image-20230315093137507

获取form参数(常用POST请求)

package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

//获取form表单提交的参数
func main() {
	r := gin.Default()
	r.POST("/login", func(c *gin.Context) {
		//username:=http.PostForm("username") //获取form表单提交的数据
		//username := c.DefaultPostForm("username", "wyx")//获取不到的话就使用默认值
		username, ok1 := c.GetPostForm("username") //获取数据和获取结果
		password, ok2 := c.GetPostForm("password")
		msg := "登录成功"
		data := make(map[string]string, 2)
		status := 200
		if !ok1 {
			msg = "请输入用户名"
			status = 500
		}
		if !ok2 {
			msg = "请输入密码"
			status = 500
		}

		data["username"] = username
		data["password"] = password
		
		c.JSON(http.StatusOK, gin.H{
			"code": status,
			"msg":  msg,
			"data": data,
		})
	})

	r.Run(":9090")
}

运行结果:

image-20230315095016148

获取path参数

package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

//获取URL路径参数
func main() {
	r := gin.Default()

	r.GET("/user/:userId/:username", func(c *gin.Context) {
		//获取路径参数
		userId := c.Param("userId")
		username := c.Param("username")
		c.JSON(http.StatusOK, gin.H{
			"userId":   userId,
			"username": username,
		})
	})

	r.Run(":9090")
}

运行结果:

image-20230315100339184

获取json参数

package main

import (
	"encoding/json"
	"github.com/gin-gonic/gin"
	"net/http"
)

func main() {
	r := gin.Default()
	r.POST("/json", func(c *gin.Context) {
		data, _ := c.GetRawData()
		//定义map或结构体
		var m map[string]interface{}
		//反序列化
		_ = json.Unmarshal(data, &m)
		c.JSON(http.StatusOK, m)
	})
	r.Run(":9090")
}

运行结果:

image-20230315101259801

绑定参数

为了能够更方便的获取请求相关参数,提高开发效率,我们可以基于请求的Content-Type识别请求数据类型并利用反射机制自动提取请求中QueryStringform表单JSONXML等参数到结构体中。 下面的示例代码演示了.ShouldBind()强大的功能,它能够基于请求自动提取JSONform表单QueryString类型的数据,并把值绑定到指定的结构体对象。

package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

type User struct {
	Username string `form:"username" json:"username" `
	Password string `form:"password" json:"password" `
}

//绑定参数
func main() {
	r := gin.Default()
	//绑定query-string参数
	r.GET("/user", func(c *gin.Context) {
		var user User              //声明一个User类型的变量user
		err := c.ShouldBind(&user) //绑定参数到结构体,这里传的一定得是地址
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"error": err.Error(),
			})
		} else {
			c.JSON(http.StatusOK, gin.H{
				"msg":  "hello",
				"data": user,
			})
		}
	})
	//绑定form参数
	r.POST("/form", func(c *gin.Context) {
		var user User              //声明一个User类型的变量user
		err := c.ShouldBind(&user) //绑定参数到结构体,这里传的一定得是地址
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"error": err.Error(),
			})
		} else {
			c.JSON(http.StatusOK, gin.H{
				"msg":  "hello",
				"data": user,
			})
		}
	})
	//绑定json数据
	r.POST("/json", func(c *gin.Context) {
		var user User
		err := c.ShouldBind(&user)
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"error": err.Error(),
			})
		} else {
			c.JSON(http.StatusOK, gin.H{
				"msg":  "hello",
				"data": user,
			})
		}
	})
	r.Run(":9090")
}

运行结果:

image-20230315104023822

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AVYMTpiu-1690859834109)(https://cdn.jsdelivr.net/gh/weiyuexin/blogimg@latest/img/2023/08/01/20230801111636.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QPkI9rcV-1690859834110)(./assets/image-20230315105106348.png)]

文件上传

package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"log"
	"net/http"
)

//文件上传
func main() {
	r := gin.Default()
	//上传单个文件
	r.POST("/upload", func(c *gin.Context) {
		//从请求中读取文件
		file, err := c.FormFile("file")
		if err != nil { //读取失败
			c.JSON(http.StatusBadRequest, gin.H{
				"error": err.Error(),
			})
		} else {
			//将读取到的文件保存到服务器,dst是保存的位置
			dst := fmt.Sprintf("C:\\Users\\30224\\Desktop\\GoLearn\\gin\\gin_06_upload_file\\%s", file.Filename)
			c.SaveUploadedFile(file, dst)
			c.JSON(http.StatusOK, gin.H{
				"status":   "ok",
				"msg":      "upload success",
				"filePath": dst,
			})
		}
	})
	//上传多个文件
	// 处理multipart forms提交文件时默认的内存限制是32 MiB
	// 可以通过下面的方式修改
	// router.MaxMultipartMemory = 8 << 20  // 8 MiB
	r.POST("/uploads", func(c *gin.Context) {
		form, _ := c.MultipartForm()
		files := form.File["file"]
		for index, file := range files {
			log.Println(file.Filename)
			dst := fmt.Sprintf("C:\\Users\\30224\\Desktop\\GoLearn\\gin\\gin_06_upload_file\\%d_%s", index, file.Filename)
			//上传文件到指定目录
			c.SaveUploadedFile(file, dst)
		}
		c.JSON(http.StatusOK, gin.H{
			"msg": fmt.Sprintf("%d files upload!", len(files)),
		})
	})
	r.Run(":9090")
}

重定向

HTTP重定向

HTTP 重定向很容易。 内部、外部重定向均支持。

//http重定向
r.GET("/index", func(c *gin.Context) {
	c.Redirect(http.StatusMovedPermanently, "https://www.baidu.com")
})

运行结果:直接跳转到新页面

路由重定向

路由重定向,使用HandleContext

//路由重定向
r.GET("/a", func(c *gin.Context) {
	//跳转到 /b 对应的路由函数
	c.Request.URL.Path = "/b" //把请求的URI修改cheng /b
	r.HandleContext(c)        //继续后续的处理
	c.JSON(http.StatusOK, gin.H{
		"msg": "a",
	})
})
r.GET("/b", func(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"msg": "b",
	})
})

运行结果:浏览器显示的地址没有变,先执行 /b 然后再执行 /a 的后续操作

image-20230315114858616

路由和路由组

普通路由

r.GET("/index", func(c *gin.Context) {...})
r.GET("/login", func(c *gin.Context) {...})
r.POST("/login", func(c *gin.Context) {...})
//请求方法集合,匹配所以请求方法
r.Any("/user", func(c *gin.Context) {
	switch c.Request.Method {
	case "GET":
		c.JSON(http.StatusOK, gin.H{"method": "GET"})
	case http.MethodPost:
		c.JSON(http.StatusOK, gin.H{"method": "POST"})
	case http.MethodDelete:
		c.JSON(http.StatusOK, gin.H{"method": "DELETE"})
	}
})

为没有配置处理函数的路由添加处理程序,默认情况下它返回404代码,下面的代码为没有匹配到路由的请求都返回views/404.html页面。

r.NoRoute(func(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{"msg": "404 not found"})
})

路由组

我们可以将拥有共同URL前缀的路由划分为一个路由组。习惯性一对{}包裹同组的路由,这只是为了看着清晰,你用不用{}包裹功能上没什么区别。

videoGroup := r.Group("/video")
{
	videoGroup.GET("/index", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"msg": "/video/index"})
	})
	videoGroup.GET("/xx", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"msg": "/video/xx"})
	})
	videoGroup.GET("/oo", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"msg": "/video/oo"})
	})
}

路由组也是支持嵌套的,例如:

shopGroup := r.Group("/shop")
	{
		shopGroup.GET("/index", func(c *gin.Context) {
			c.JSON(http.StatusOK, gin.H{"msg": "/shop/index"})
		})
		xx := shopGroup.Group("/xx")
		{
			xx.GET("/oo", func(c *gin.Context) {
				c.JSON(http.StatusOK, gin.H{"msg": "/shop/xx/oo"})
			})
		}
	}

Gin中间件

Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。

定义中间件

Gin中的中间件必须是一个gin.HandlerFunc类型。

// StatCost 是一个统计耗时请求耗时的中间件
func StatCost() gin.HandlerFunc {
	return func(c *gin.Context) {
		start := time.Now()
		c.Set("name", "小王子") // 可以通过c.Set在请求上下文中设置值,后续的处理函数能够取到该值
		// 调用该请求的剩余处理程序
		c.Next()
		// 不调用该请求的剩余处理程序
		// c.Abort()
		// 计算耗时
		cost := time.Since(start)
		log.Println(cost)
	}
}
//记录响应体的中间件
type bodyLogWriter struct {
	gin.ResponseWriter               // 嵌入gin框架ResponseWriter
	body               *bytes.Buffer // 我们记录用的response
}

// Write 写入响应体数据
func (w bodyLogWriter) Write(b []byte) (int, error) {
	w.body.Write(b)                  // 我们记录一份
	return w.ResponseWriter.Write(b) // 真正写入响应
}

// ginBodyLogMiddleware 一个记录返回给客户端响应体的中间件
// https://stackoverflow.com/questions/38501325/how-to-log-response-body-in-gin
func ginBodyLogMiddleware(c *gin.Context) {
	blw := &bodyLogWriter{body: bytes.NewBuffer([]byte{}), ResponseWriter: c.Writer}
	c.Writer = blw // 使用我们自定义的类型替换默认的

	c.Next() // 执行业务逻辑

	fmt.Println("Response body: " + blw.body.String()) // 事后按需记录返回的响应
}

注册中间件

在gin框架中,我们可以为每个路由添加任意数量的中间件。

为全局路由注册
func main() {
	// 新建一个没有任何默认中间件的路由
	r := gin.New()
	// 注册一个全局中间件
	r.Use(StatCost())
	
	r.GET("/test", func(c *gin.Context) {
		name := c.MustGet("name").(string) // 从上下文取值
		log.Println(name)
		c.JSON(http.StatusOK, gin.H{
			"message": "Hello world!",
		})
	})
	r.Run()
}
为某个路由单独注册
// 给/test2路由单独注册中间件(可注册多个)
r.GET("/test2", StatCost(), func(c *gin.Context) {
	name := c.MustGet("name").(string) // 从上下文取值
	log.Println(name)
	c.JSON(http.StatusOK, gin.H{
		"message": "Hello world!",
	})
})
为路由组注册中间件
//方法一
shopGroup := r.Group("/shop", StatCost())
{
    shopGroup.GET("/index", func(c *gin.Context) {...})
    ...
}
//方法二
shopGroup := r.Group("/shop")
shopGroup.Use(StatCost())
{
    shopGroup.GET("/index", func(c *gin.Context) {...})
    ...
}

中间件使用事项

Gin默认中间件

gin.Default()默认使用了LoggerRecovery中间件,其中:

  • Logger中间件将日志写入gin.DefaultWriter,即使配置了GIN_MODE=release
  • Recovery中间件会recover任何panic。如果有panic的话,会写入500响应码。

如果不想使用上面两个默认的中间件,可以使用gin.New()新建一个没有任何默认中间件的路由。

gin中间件中使用goroutine

当在中间件或handler中启动新的goroutine时,不能使用原始的上下文(c *gin.Context),必须使用其只读副本(c.Copy())。

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

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

相关文章

Vivado进行自定义IP封装

一. 简介 本篇文章将介绍如何使用Vivado来对上篇文章(FPGA驱动SPI屏幕)中的代码进行一个IP封装&#xff0c;Vivado自带的IP核应该都使用过&#xff0c;非常方便。 这里将其封装成IP核的目的主要是为了后续项目的调用&#xff0c;否则当我新建一个项目的时候&#xff0c;我需要将…

实现 rollup 实现多模块打包

rollup 是一个 JavaScript 模块打包器&#xff0c;可以将许多 JavaScript 库和应用程序打包成少量的捆绑包&#xff0c;从而提高了应用程序的性能。本文详细描述如何通过 rollup 实现多模块打包。 前提 项目的目录结构 先看下项目的 package.json 文件夹&#xff1a; {&qu…

LLaMA系列 | LLaMA和LLaMA-2精简总结

文章目录 1、LLaMA1.1、模型结构1.2、训练方式1.3、结论 2、LLaMA-22.1、相比LLaMA1的升级2.3、模型结构2.3.1、MHA, MQA, GQA区别与联系 2.4、训练方式 1、LLaMA &#x1f525; 纯基座语言模型 《LLaMA: Open and Efficient Foundation Language Models》&#xff1a;https:/…

CS5801国产HDMI转DP/edp(4k60)转换器方案芯片 可替代LT6711

CS5801是HDMI2.0b到DP1.4a转换器方案IC。CS5801 有一个HDMI2.0b .输入&#xff0c;带宽高达18Gbps.它支持辨别率是4k60Hz。对于DP1.4输出&#xff0c;由4条数据通道组成&#xff0c;支持1.62Gbps、 2.7Gbps、 5.4Gbps链路速率。内置可选SSC功能可降低EMI影响。嵌入式MCU基于32位…

3d虚拟vr汽车实景展厅吸引更多潜在消费者

随着人们对生活品质的追求&#xff0c;越来越多的消费者开始关注汽车的外观设计、内饰配置等方面。传统的展示方式已经不能满足消费者的需求&#xff0c;车辆VR虚拟漫游展示应运而生。借助VR虚拟现实和web3d开发建模技术&#xff0c;对汽车的外观、造型及信息数据进行数字化处理…

乳腺癌CT影像数据的深度学习:R语言与ANN神经网络构建高性能分类诊断模型

一、引言 乳腺癌是全球最常见的女性恶性肿瘤之一&#xff0c;也影响着男性的健康。据统计&#xff0c;每年有数百万人被诊断出患有乳腺癌[1]。乳腺癌的早期检测和准确诊断对于治疗和预后至关重要。然而&#xff0c;乳腺癌的早期诊断面临许多挑战&#xff0c;如图像解读的主观性…

第一堂棒球课:MLB全明星发展历程·棒球1号位

MLB全明星发展历程 1. MLB全明星的起源 MLB全明星是什么&#xff1f; MLB全明星&#xff0c;也就是MLB All-Stars&#xff0c;是指美国职业棒球大联盟&#xff08;Major League Baseball, MLB&#xff09;在每年举办的全明星赛。这项赛事汇集了全联盟各队的顶级球员&#xff…

InnoDB存储引擎——事务原理

1.什么是事务 2.redo log 脏页是指缓冲区的数据与磁盘中的数据不一致时的状态。脏页的数据并不是实时刷新的&#xff0c;而是一段时间之后通过后台线程把脏页的数据刷线到磁盘&#xff0c;假如说脏页的数据在往磁盘中刷新的时候出错了&#xff0c;内存中的数据没有刷新到磁盘当…

Java8实战-总结11

Java8实战-总结11 Lambda表达式方法引用管中窥豹如何构建方法引用 构造函数引用 Lambda表达式 方法引用 方法引用让你可以重复使用现有的方法定义&#xff0c;并像Lambda一样传递它们。在一些情况下&#xff0c;比起使用Lambda表达式&#xff0c;它们似乎更易读&#xff0c;感…

代码随想录算法训练营第二十九天 | Leetcode随机抽题检测

Leetcode随机抽题检测 160 相交链表未看解答自己编写的青春版重点题解的代码206 反转链表 一段用于复制的标题未看解答自己编写的青春版重点题解的代码日后再次复习重新写 234 回文链表未看解答自己编写的青春版重点综上&#xff0c;利用快慢指针找寻链表中间&#xff0c;就按加…

【C++】总结9

文章目录 C从源代码到可执行程序经过什么步骤静态链接和动态链接类的对象存储空间C的内存分区内存池在成员函数中调用delete this会出现什么问题&#xff1f;如果在类的析构函数中调用delete this&#xff0c;会发生什么&#xff1f; C从源代码到可执行程序经过什么步骤 预处理…

Tomcat 创建https

打开CMD,按下列输入 keytool -genkeypair -alias www.bo.org -keyalg RSA -keystore d:\ambition.keystore -storetype pkcs12 输入密钥库口令:123456 再次输入新口令:123456 您的名字与姓氏是什么? [Unknown]: www.ambition.com 您的组织单位名称是什么? [Unknown…

Qt 编译 Android 项目,输出乱码

乱码如下&#xff1a; :-1: error: 娉 C:\Qt\6.5.0\android_arm64_v8a\src\android\java\src\org\qtproject\qt\android\bindings\QtActivity.java浣跨敤鎴栬鐩栦簡宸茶繃鏃剁殑 API銆 娉 鏈夊叧璇︾粏淇℃伅, 璇蜂娇鐢-Xlint:deprecation 閲嶆柊缂栬瘧銆 正确的应该是&#…

qemu kvm 新建虚拟机

开始菜单打开虚拟机管理器

HDFS集群滚动升级以及回滚相关

HDFS集群滚动升级以及回滚相关 介绍不停机滚动升级非联邦HA集群联邦HA集群 停机升级--非HA集群HDFS集群降级和回滚异同点共同点不同点 HA集群降级&#xff08;downgrade&#xff09;注意事项 集群回滚操作 介绍 在hadoop v2中&#xff0c;HDFS支持namenode高可用&#xff08;H…

neo4j使用中的常见问题

1Spring Boot NEO The client is unauthorized due to authentication failure 解决方法&#xff1a;找到你安装neo4j的路径下的conf文件夹&#xff0c;找到neo4j.conf #dbms.security.auth_enabledfalse将前面的注释#去掉&#xff0c;然后重启neo4j&#xff0c;在重启项目即…

el-cascader级联选择器加载远程数据、默认开始加载固定条、可以根据搜索加载远程数据。

加载用户列表分页请求、默认请求20条数据。想添加远程搜索用户功能。原有的方法filter-method不能监听到输入清空数据的时候。这样搜索完无法返回默认的20条数据。直接监听级联选择的v-model绑定的值是无法检测到用户自己输入的。 解决思路&#xff1a; el-cascader 没有提供…

屏蔽托盘右键菜单

最近有个需求需要屏蔽托盘图标的右下角菜单项&#xff1a; 经过Apimonitor进行hook Explorer进程&#xff0c;发现弹出菜单是通过explorer调用InserMenuItem函数来实现的。通过注入explorer并挂钩InserMenuItemW函数&#xff0c;并屏蔽自己想要屏蔽的菜单项&#xff1a; &#…

2023-08-01 LeetCode每日一题(英雄的力量)

2023-08-01每日一题 一、题目编号 2681. 英雄的力量二、题目链接 点击跳转到题目位置 三、题目描述 给你一个下标从 0 开始的整数数组 nums &#xff0c;它表示英雄的能力值。如果我们选出一部分英雄&#xff0c;这组英雄的 力量 定义为&#xff1a; i0 &#xff0c;i1 &…

【ARM Coresight 系列文章 2.5 - Coresight 寄存器:PIDR0-PIDR7,CIDR0-CIDR3 介绍】

文章目录 1.1 JEDEC 与 JEP1061.2 PIDR0-PIDR7(peripheral identification registers)1.2 CIDR0-CIDR3(Component Identification Registers) 1.1 JEDEC 与 JEP106 JEDEC和JEP106都是来自美国电子工业联合会&#xff08;JEDEC&#xff0c;Joint Electron Device Engineering C…