Go语言Gin框架Logrus日志管理和token身份验证

news2024/11/14 14:29:30

文章目录

  • Logrus日志管理
    • Logrus基本用法
    • 实现日志切割和过期删除
  • token身份验证
    • Hash消息认证签名实现token
    • RSA签名实现token
    • 椭圆曲线数字签名算法(ECDSA)方式生成token

Logrus日志管理

Logrus是一个结构化的Go日志框架,功能强大,具有高度的灵活性,它提供了自定义插件的功能,有TEXT与JSON两种可选的日志输出格式。Logrus还支持Field机制和可扩展的HooK机制,它鼓励用户通过Field机制进行精细化的、结构化的日志记录,允许用户通过hook的方式将日志分到任意地方。

许多著名开源项目,如docker、prometheus等都是使用Logrus来记录日志。

Logrus基本用法

添加依赖:go get github.com/sirupsen/logrus,代码如下:

//router
r.GET("/logrusDemo1", controllers.LogrusDemo1)

//controllers
import (
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/sirupsen/logrus"
	"net/http"
	"os"
)

var log = logrus.New() // 创建一个log示例
func initLogrus() error {
	log.Formatter = &logrus.JSONFormatter{} // 设置为json格式的日志

	//os.O_CREATE: 如果文件不存在则创建
	//os.O_WRONLY: 以读写的方式打开
	//os.O_APPEND: 追加写入
	//0644: 文件权限
	file, err := os.OpenFile("./logs/http_api.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) // 创建一个log日志文件
	if err != nil {
		fmt.Println("文件打开或创建失败")
		return err
	}
	log.Out = file               // 设置log的默认文件输出
	gin.SetMode(gin.ReleaseMode) // 发布版本
	gin.DefaultWriter = log.Out  // 如果设置此项,gin框架自己记录的日志也会写入到文件
	log.Level = logrus.InfoLevel // 设置日志级别
	return nil
}

// 文档地址:https://github.com/sirupsen/logrus
func LogrusDemo1(c *gin.Context) {
	err := initLogrus()
	if err != nil {
		fmt.Println(err)
		return
	}

	//log日志信息的写入
	log.WithFields(logrus.Fields{
		"url":    c.Request.RequestURI, //自定义显示的字段,下同
		"method": c.Request.Method,
		"params": c.Query("name"),
		"IP":     c.ClientIP(),
	}).Info()
	resData := struct {
		Code int         `json:"code"`
		Msg  string      `json:"msg"`
		Data interface{} `json:"data"`
	}{http.StatusOK, "响应成功", "OK"}
	c.JSON(http.StatusOK, resData)
}

运行后,可以看到指定目录下已经生成了JSON格式的日志:
在这里插入图片描述

实现日志切割和过期删除

如果想要将日志保存为每一天一个文件,并且设置超过一段时间(比如7天)之前的日志自动删除,可以使用下面两个依赖实现:

  • 使用 go get github.com/lestrrat-go/file-rotatelogs 实现日志文件切割
  • 使用 go get github.com/rifflock/lfshook 实现日志文件的hook机制

具体实现代码如下:

//router
r.Use(controllers.LogMiddleware())
r.GET("/LogrusDemo2", controllers.LogrusDemo2)

//controllers
import (
	"fmt"
	"github.com/gin-gonic/gin"
	rotatelogs "github.com/lestrrat-go/file-rotatelogs"
	"github.com/rifflock/lfshook"
	"github.com/sirupsen/logrus"
	"net/http"
	"os"
	"path"
	"time"
)
// 日志文件自动切割以及过期删除
func LogMiddleware() gin.HandlerFunc {
	var (
		logFilePath = "./logs" //文件存储路径
		logFileName = "system.log"
	)
	// 日志文件
	fileName := path.Join(logFilePath, logFileName)
	// 写入文件
	file, err := os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
	if err != nil {
		fmt.Println(err)
	}
	// 实例化
	logger := logrus.New()
	//设置日志级别
	logger.SetLevel(logrus.DebugLevel)
	//设置输出
	logger.Out = file
	// 设置 rotatelogs,实现文件分割
	logWriter, err := rotatelogs.New(
		// 分割后的文件名称
		fileName+".%Y%m%d.log",
		// 生成软链,指向最新日志文件
		rotatelogs.WithLinkName(fileName),
		// 设置最大保存时间(7天)
		rotatelogs.WithMaxAge(7*24*time.Hour), //以hour为单位的整数
		// 设置日志切割时间间隔(1天)
		rotatelogs.WithRotationTime(1*time.Hour),
	)
	//hook机制的设置
	writerMap := lfshook.WriterMap{
		logrus.InfoLevel:  logWriter,
		logrus.FatalLevel: logWriter,
		logrus.DebugLevel: logWriter,
		logrus.WarnLevel:  logWriter,
		logrus.ErrorLevel: logWriter,
		logrus.PanicLevel: logWriter,
	}
	//给logrus添加hook
	logger.AddHook(lfshook.NewHook(writerMap, &logrus.JSONFormatter{
		TimestampFormat: "2006-01-02 15:04:05",
	}))
	return func(c *gin.Context) {
		c.Next()
		//请求方式
		method := c.Request.Method
		//请求路由
		reqUrl := c.Request.RequestURI
		//状态码
		statusCode := c.Writer.Status()
		//请求ip
		clientIP := c.ClientIP()
		logger.WithFields(logrus.Fields{
			"status_code": statusCode,
			"client_ip":   clientIP,
			"req_method":  method,
			"req_uri":     reqUrl,
		}).Info()
	}

}
func LogrusDemo2(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"code": 200,
		"msg":  "响应成功",
		"data": "OK",
	})
}

运行后,可以看到指定目录下已经生成了按照日期存储的日志文件:
在这里插入图片描述

token身份验证

token是一种验证客户端请求参数是否合法的方式,最简单的token组成:uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名)。

token和session的区别

  • token和session都是用来校验客户端请求的数据是否合法,session一般翻译为会话,而token更多的时候是翻译为令牌;
  • session可以保存在服务器的缓存、文件、数据库等,session和token都可以设置过期时间。
  • 其实token与session在时间与空间的侧重点不同,session是空间换时间,而token是时间换
    空间。两者的选择要看具体情况而定。
  • 对于token,服务端不需要记录任何状态,每次客户端的请求都是无状态的,每次解密验证然后判断请求是否合法。

token验证方式

The HMAC signing method(HS256,HS384,HS512)//Hash消息认证签名
The RSA signing method(RS256,RS384,RS512)//RSA非对称加密签名
The ECDSA signing method(ES256,ES384,ES512)//椭圆曲线数字签名

文档地址:https://github.com/dgrijalva/jwt-go

Hash消息认证签名实现token

通过一个自定义的字符串加密,使用jwt-go来生成和校验token是否有效。添加依赖go get github.com/dgrijalva/jwt-go

代码如下:

//router
r3.POST("hmac/createToken", token.HmacCreateToken)

//controllers
package token

import (
	"fmt"
	"github.com/dgrijalva/jwt-go"
	"github.com/gin-gonic/gin"
	"net/http"
	"time"
)

// Hash消息认证签名实现token
type HmacUser struct {
	UserId   string `json:"user_id"` //根据用户id生成token
	UserName string `json:"user_name"`
}
type MyClaims struct {
	UserId string
	jwt.StandardClaims
}

var jwtKey = []byte("secret_renxing_csdn_gin") //证书签名秘钥,用来签发证书

func HmacCreateToken(c *gin.Context) {
	var user HmacUser
	c.Bind(&user) //绑定客户端传来的参数
	fmt.Println("user", user)
	if user.UserId == "" {
		c.JSON(http.StatusBadRequest, "user_id不能为空")
		return
	}
	token, err := _createToken(user)
	if err != nil {
		c.JSON(http.StatusInternalServerError, err)
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"code": http.StatusOK,
		"msg":  "token创建成功",
		"data": gin.H{
			"user_id": user.UserId,
			"token":   token,
		},
	})
}

// 创建token
func _createToken(user HmacUser) (string, error) {
	expirationTime := time.Now().Add(7 * 24 * time.Hour) //token有效期截止时间,从当前时间往后7天
	claims := &MyClaims{
		UserId: user.UserId,
		StandardClaims: jwt.StandardClaims{
			ExpiresAt: expirationTime.Unix(),    //过期时间
			IssuedAt:  time.Now().Unix(),        //发布时间
			Subject:   "create hmac token demo", //主题
			Issuer:    "renxing",                //发布者
		},
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) //生成token
	tokenString, err := token.SignedString(jwtKey)
	if err != nil {
		return "", err
	}
	return tokenString, nil
}

在这里插入图片描述
通过上面的方法可以生成一个token,然后还需要实现校验token是否有效,代码如下:

//router
r3.POST("hmac/checkToken", token.HmacAuthMiddleware(), func(c *gin.Context) {
	c.String(http.StatusOK, "token验证通过")
})

//controllers
// 校验token
func HmacAuthMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		auth := "renxing"
		// 获取authorization header
		Authorization := c.GetHeader("Authorization") //客户端传递的header信息,header的key是Authorization
		fmt.Println("header:Authorization", Authorization)
		if Authorization == "" || !strings.HasPrefix(Authorization, auth+":") { //验证token不为空,并且以 renxing: 为前缀
			c.JSON(http.StatusUnauthorized, gin.H{"code": http.StatusUnauthorized, "msg": "解析token前缀错误"})
			c.Abort()
			return
		}
		index := strings.Index(Authorization, auth+":") //找到token前缀对应的位置
		//真实token的值
		tokenString := Authorization[index+len(auth)+1:] //截取真实的token(开始位置为:索引开始的位置+关键字符的长度+1(:的长度为1))
		fmt.Println("tokenString", tokenString)

		//解析token
		token, claims, err := _hamcParseToke(tokenString)
		fmt.Println("token", token)
		fmt.Println("claims", claims)
		if err != nil || !token.Valid { //解析错误或者过期等
			fmt.Println("err", err)
			c.JSON(http.StatusUnauthorized, gin.H{"code": http.StatusUnauthorized, "msg": "证书无效"})
			c.Abort()
			return
		}

		//校验比对解析后的token中的user_id是否和客户端传来的user_id匹配
		var user HmacUser
		c.Bind(&user)
		if user.UserId != claims.UserId {
			c.JSON(http.StatusUnauthorized, gin.H{"code": http.StatusUnauthorized, "msg": "用户不存在"})
			c.Abort()
			return
		}
		c.Next()
	}
}

// 解析token
func _hamcParseToke(tokenString string) (*jwt.Token, *MyClaims, error) {
	claims := &MyClaims{}
	token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (i interface{}, err error) {
		return jwtKey, nil
	})
	return token, claims, err
}

在这里插入图片描述
控制台输出
在这里插入图片描述

RSA签名实现token

RSA加密是一种非对称加密,可以在不直接传递密钥的情况下完成解密,这能够确保信息的安全性,避免了直接传递密钥所造成的被破解的风险。RSA是由一对密钥来进行加解密的过程,分别称为公钥和私钥。RSA加密算法的原理就是对一极大整数做因数分解的困难性来保证安全性,通常个人保存私钥,公钥是公开的(可能同时多人持有)。
http://www.metools.info/code/c80.html 这个网站可以生成公钥私钥对:
在这里插入图片描述
接下来,在Gin框架中实现RSA加密生成token。首先将上面生成的公钥和私钥保存到项目中的两个文件,命名为.pem结尾。

代码如下:

//router
r3.POST("rsa/createToken", token.RsaCreateToken)
r3.POST("rsa/checkToken", token.RsaAuthMiddleware(), func(c *gin.Context) {
	c.String(http.StatusOK, "RSA token验证通过")
})

//controllers
package token

import (
	"fmt"
	"github.com/dgrijalva/jwt-go"
	"github.com/gin-gonic/gin"
	"io/ioutil"
	"net/http"
	"strings"
	"time"
)

// RSA签名实现token
// RAS密钥生成工具链接:http://www.metools.info/code/c80.html
type RsaUser struct {
	UserId   string `json:"user_id"` //根据用户id生成token
	UserName string `json:"user_name"`
}
type RasClaims struct {
	UserId string `json:"user_id"`
	jwt.StandardClaims
}

var (
	resPrivateKey []byte //读取私钥内容
	resPublicKey  []byte //读取公钥内容
	err1, err2    error
)

// 初始化读取秘钥文件内容
func init() {
	resPrivateKey, err1 = ioutil.ReadFile("/home/rx/go/gin-demo/token/pem/privateKey.pem") //路径从GOPATH开始
	resPublicKey, err2 = ioutil.ReadFile("/home/rx/go/gin-demo/token/pem/publicKey.pem")
	if err1 != nil || err2 != nil {
		panic(fmt.Sprintf("读取秘钥文件内容出错:%s,%s", err1, err2))
		return
	}
}
func RsaCreateToken(c *gin.Context) {
	var user RsaUser
	c.Bind(&user) //绑定客户端传来的参数
	fmt.Println("user", user)
	if user.UserId == "" {
		c.JSON(http.StatusBadRequest, "user_id不能为空")
		return
	}
	token, err := _createRsaToken(user)
	if err != nil {
		c.JSON(http.StatusInternalServerError, err)
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"code": http.StatusOK,
		"msg":  "token创建成功",
		"data": gin.H{
			"user_id": user.UserId,
			"token":   token,
		},
	})
}

// 创建token
func _createRsaToken(user RsaUser) (interface{}, error) {
	privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(resPrivateKey)
	if err != nil {
		return nil, err
	}
	claims := &RasClaims{
		UserId: user.UserId,
		StandardClaims: jwt.StandardClaims{
			ExpiresAt: time.Now().Add(7 * 24 * time.Hour).Unix(), //token有效期截止时间,从当前时间往后7天
			Issuer:    "renxing",                                 //发布者
		},
	}
	token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
	signedString, err := token.SignedString(privateKey)
	return signedString, err
}

// 校验token
func RsaAuthMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		auth := "renxing"
		tokenString := c.GetHeader("Authorization")
		if tokenString == "" || !strings.HasPrefix(tokenString, auth+":") {
			c.JSON(http.StatusUnauthorized, gin.H{"code": http.StatusUnauthorized, "msg": "无效的token"})
			c.Abort()
			return
		}
		index := strings.Index(tokenString, auth+":")
		tokenString = tokenString[index+len(auth)+1:]
		claims, err := _rsaParseToken(tokenString)
		if err != nil {
			c.JSON(http.StatusUnauthorized, gin.H{"code": http.StatusUnauthorized, "msg": "证书无效"})
			c.Abort()
			return
		}
		claimsValue := claims.(jwt.MapClaims) //断言
		if claimsValue["user_id"] == nil {
			c.JSON(http.StatusUnauthorized, gin.H{"code": http.StatusUnauthorized, "msg": "用户不存在"})
			c.Abort()
			return
		}
		u := RsaUser{}
		c.Bind(&u)
		id := claimsValue["user_id"].(string)
		if u.UserId != id {
			c.JSON(http.StatusUnauthorized, gin.H{"code": http.StatusUnauthorized, "msg": "用户Id不匹配"})
			c.Abort()
			return
		}
		c.Next()
	}
}

// 解析token
func _rsaParseToken(tokenString string) (interface{}, error) {
	pem, err := jwt.ParseRSAPublicKeyFromPEM(resPublicKey)
	if err != nil {
		return nil, err
	}
	token, err := jwt.Parse(tokenString, func(token *jwt.Token) (i interface{}, err error) {
		if _, OK := token.Method.(*jwt.SigningMethodRSA); !OK {
			return nil, fmt.Errorf("解析的方法错误")
		}
		return pem, err
	})
	if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
		return claims, nil
	}
	return nil, err
}

运行效果:
在这里插入图片描述
在这里插入图片描述

椭圆曲线数字签名算法(ECDSA)方式生成token

椭圆曲线加密算法,是基于椭圆曲线数学理论实现的一种非对称加密算法。相比RSA,ECDSA优势是可以使用更短的密钥,来实现与RSA相当或更高的安全,RSA加密算法也是一种非对称加密算法,在公开密钥加密和电子商业中RSA被广泛使用。更多信息请参考 这里。

下面是实现的代码:

//router
r3.POST("ecdsa/createToken", token.EcdsaCreateToken)
r3.POST("ecdsa/checkToken", token.EcdsaAuthMiddleware, func(c *gin.Context) {
	c.String(http.StatusOK, "ECDSA token验证通过")
})

//controllers
package token

import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"errors"
	"fmt"
	"github.com/dgrijalva/jwt-go"
	"github.com/gin-gonic/gin"
	"net/http"
	"strings"
	"time"
)

type EcdsaUser struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}
type EcdsaClaims struct {
	UserId string `json:"user_id"`
	jwt.StandardClaims
}

var (
	err3          error
	eccPrivateKey *ecdsa.PrivateKey
	eccPublicKey  *ecdsa.PublicKey
)

func init() {
	eccPrivateKey, eccPublicKey, err3 = getEcdsaKey(2)
	if err3 != nil {
		panic(err3)
		return
	}
}

// ecdsa秘钥生成
func getEcdsaKey(keyType int) (*ecdsa.PrivateKey, *ecdsa.PublicKey, error) {
	var err error
	var pri_key *ecdsa.PrivateKey
	var pub_key *ecdsa.PublicKey
	var curve elliptic.Curve //椭圆曲线
	switch keyType {
	case 1:
		curve = elliptic.P224()
	case 2:
		curve = elliptic.P256()
	case 3:
		curve = elliptic.P384()
	case 4:
		curve = elliptic.P521()
	default:
		err = errors.New("输入的签名key类型错误!key取值:\n 1:椭圆曲线224\n 2:椭圆曲线2256\n 3:椭圆曲线384\n 4:椭圆曲线521\n")
		return nil, nil, err
	}
	pri_key, err = ecdsa.GenerateKey(curve, rand.Reader)
	if err != nil {
		return nil, nil, err
	}
	pub_key = &pri_key.PublicKey
	return pri_key, pub_key, err
}

func EcdsaCreateToken(c *gin.Context) {
	u := EcdsaUser{}
	err := c.Bind(&u)
	if err != nil {
		c.JSON(http.StatusBadRequest, "参数错误")
		return
	}
	token, err := _ecdsaCreateToken(u)
	if err != nil {
		c.JSON(http.StatusBadRequest, "生成token错误")
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"code": http.StatusOK,
		"msg":  "授权成功",
		"data": token,
	})
}

// 生成token
func _ecdsaCreateToken(u EcdsaUser) (interface{}, error) {
	claims := &EcdsaClaims{
		UserId: u.ID,
		StandardClaims: jwt.StandardClaims{
			ExpiresAt: time.Now().Add(7 * 24 * time.Hour).Unix(),
			Issuer:    "renxing",
		},
	}
	token := jwt.NewWithClaims(jwt.SigningMethodES256, claims)
	signedString, err := token.SignedString(eccPrivateKey)
	return signedString, err
}

// token认证中间件
func EcdsaAuthMiddleware(c *gin.Context) {
	auth := "renxing"
	tokenString := c.GetHeader("Authorization")
	if tokenString == "" || !strings.HasPrefix(tokenString, auth+":") {
		c.JSON(http.StatusUnauthorized, gin.H{"code": http.StatusUnauthorized, "msg": "无效的token"})
		c.Abort()
		return
	}
	index := strings.Index(tokenString, auth+":")
	tokenString = tokenString[index+len(auth)+1:]
	claims, err := _ecdsaJwtTokenRead(tokenString)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusUnauthorized, err)
		return
	}
	claimsValue := claims.(jwt.MapClaims)
	if claimsValue["user_id"] == nil {
		c.AbortWithStatusJSON(http.StatusUnauthorized, "id不存在")
		return
	}
	u := EcdsaUser{}
	c.Bind(&u)
	if u.ID != claimsValue["user_id"] {
		c.JSON(http.StatusUnauthorized, gin.H{"code": http.StatusUnauthorized, "msg": "用户不存在"})
		c.Abort()
		return
	}
	c.Next()
}

// token解析
func _ecdsaJwtTokenRead(tokenString string) (interface{}, error) {
	myToken, err := jwt.Parse(tokenString, func(token *jwt.Token) (i interface{}, err error) {
		if _, ok := token.Method.(*jwt.SigningMethodECDSA); !ok {
			return nil, fmt.Errorf("无效的签名方法: %v", token.Method)
		}
		return eccPublicKey, nil
	})
	if claims, ok := myToken.Claims.(jwt.MapClaims); ok && myToken.Valid {
		return claims, nil
	}
	return nil, err
}

运行效果:
在这里插入图片描述
在这里插入图片描述

源代码:https://gitee.com/rxbook/gin-demo

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

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

相关文章

机器学习2:决策树--基于信息增益的ID3算法

1.决策树的简介 建立决策树的过程可以分为以下几个步骤: 计算每个特征的信息增益或信息增益比,选择最优的特征作为当前节点的划分标准。根据选择的特征将数据集划分为不同的子集。对每个子集递归执行步骤 1 和步骤 2,直到满足终止条件。构建决策树,并输出。基于信息增益的…

Sqlserver两个服务器数据库拷贝

一.当前条件: serverA 为源服务器 serverB 为目标服务器 想把serverA中的数据库test拷贝到serverB上。 方法一: 1.在serverB上建立空数据库test 2.右键点击serverA上的test ,任务——导出数据,之后填写目标服务器就行

[论文阅读]Voxel R-CNN——迈向高性能基于体素的3D目标检测

Voxel R-CNN Voxel R-CNN: Towards High Performance Voxel-based 3D Object Detection 迈向高性能基于体素的3D目标检测 论文网址:Voxel R-CNN 论文代码:Voxel R-CNN 简读论文 该论文提出了 Voxel R-CNN,这是一种基于体素的高性能 3D 对象…

一文读懂什么是新一代BPM

什么是BPM 业务流程管理 (Business Process Management,简称BPM),是一门学科,它通过分析、建模、执行、监控、优化等流程来改进业务流程,使企业核心业务流程最优化。BPM系统即根据业务环境的变化,推进人与人之间、人与…

Modelsim 使用教程(1)——概述

目录 一、概述 二、设计优化(Design Optimizations) 三、基本仿真流程(Basic simulation flow) 3.1 创建工作库(create a working library) 3.2 编译设计(Compiling Your Design&#xff09…

MAXENT模型的生物多样性生境模拟与保护优先区甄选、自然保护区布局优化评估

随着生物多样性全球大会的举办,不论是管理机构及科研单位、高校都在积极准备,根据国家林草局最新工作指示,我国将积极整合、优化自然保护地,加快推进国家公园体制试点,构建以国家公园为主体的自然保护地体系。针对我国…

电脑剪辑完成的视频怎么传到手机

现今手机基本上可以完成电脑的所有操作,甚至功能会更加丰富,但是对于需要剪辑视频的用户来说,电脑无疑仍然是最佳选择,毕竟屏幕大,对于视频细节的把控更到位,查看整体效果不容易出现问题,然而当…

Asset private data

其使用的java链码是基于gradle7.0的。相比于其他环境下的java代码,多了一些限制: (1)逗号后面必须有空格,花括号前面必须有空格,运算符前后必须有空格 (2)类里面的set方法参数不能和类里面的成员变量名字…

深入了解Python类与面向对象编程

在编程世界中,面向对象编程(OOP)是一种强大的编程范式,而Python是一门优雅而强大的编程语言。本文将带你深入探讨Python中的类与面向对象,为你揭示面向对象编程的奇妙世界。 类与对象的概念 1.什么是类? …

21款奔驰E300L升级HUD抬头显示 直视仪表信息

随着科技飞速地发展,从汽车领域就可以看出,尤其是汽车的抬头显示器,一经推出就吸引了很多的车主。 升级HUD抬头显示,HUD与汽车系统进行完整的数据信息连接,整合成大数据,然后将一些重要信息映射到车窗玻璃上…

Netflix app客户端有哪些优势?稳定奈飞安装包

Netflix app客户端软件是一款提供海量高质量影视内容的视频播放软件,用户可以轻松浏览和观看电影、电视剧、纪录片等,享受高清画质和个性化推荐服务,同时支持多设备使用,为用户带来优质的观影体验。 Netflix app客户端软件的优势…

如何用思维导图开会

在办公室和会议室使用思维导图会有无数好处。今天我们就聊聊思维导图在开会中的作用? 为什么要在会议中使用思维导图? 思维导图可以帮助我们整理思路。会议通常涉及到复杂的议题和讨论,使用思维导图可以帮助整合和梳理参与者的思路和观点。通…

minio 分片上传限制

引用官方文档,可以直接查看官方链接地址 Thresholds and Limits — MinIO Object Storage for Kubernetes

视频剪辑高手:画中画叠加批量处理技巧,剪辑更高效,更省心

随着数字媒体的普及,视频剪辑已经成为许多人的必备技能。然而,面对海量的视频素材和繁琐的剪辑工作,如何高效、省心地完成视频剪辑成为了一个重要的问题。本文将介绍云炫AI智剪“画中画叠加批量处理”的技巧,帮助你轻松提升视频剪…

Matlab中的app设计

1.窗口焦点问题: 窗口焦点问题:确保你的应用程序窗口正常处于焦点状态。有时,其他窗口的弹出或焦点切换可能导致应用程序最小化。点击应用程序窗口以确保它处于焦点状态。 窗口管理:确保你的 MATLAB 或操作系统没有未处理的错误或…

fastdfs实现防盗链功能

目录 一、实现原理 二、开启防盗链 三、重启nginx 四、 Java代码生成token 1、token生成规则 2、java生成token 五、测试 1 带正确token访问 2 带错误token访问,或直接访问 一、实现原理 fastdfs是一个分布式文件系统,如果我们的fastdfs部署在外…

超实用的企业防范数据泄露小技巧!

超实用的企业防范数据泄露小技巧! 小技巧1、加强员工培训,提高防范思想 及时向员工传达有关安全信息,加强员工意识、认识和执行安全措施,以防止数据泄露发生。 小技巧2、建立安全政策,明确处理流程 企业应该建立安…

基于RFID技术的智能医疗柜管理系统解决方案

一、行业背景 当前,医疗机构面临着药物过期、召回、空货架、放错产品和库存缺失等问题,这些问题导致医院项目效率低下,患者护理不足,医护人员备受困扰。为了应对全球医疗体系面临的压力,医疗保健机构急需寻找新技术来…

C++中vec.size()-1的坑

问题描述&#xff1a;如下代码&#xff0c; #include <iostream> #include <vector>using namespace std;int main() {vector<int> vec {};for (int i 0; i < vec.size() - 1; i) {cout << "i " << i << ", vec[i] …

生成瑞利信道(Python and Matlab)

channel h k h_k hk​ is modeled as independent Rayleigh fading with average power loss set as 10^−3 Python import numpy as np# Set the parameters average_power_loss 1e-3 # Average power loss (10^(-3)) num_samples 1000 # Number of fading samples to …