Go项目实现日志按时间及文件大小切割并压缩


关于日志的一些问题:

单个文件过大会影响写入效率,所以会做拆分,但是到多大拆分? 最多保留几个日志文件?最多保留多少天,要不要做压缩处理?

一般都使用 lumberjack[1]这个库完成上述这些操作


lumberjack


 //info文件writeSyncer
 infoFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
  Filename:   "./log/info.log"//日志文件存放目录,如果文件夹不存在会自动创建
  MaxSize:    2,                //文件大小限制,单位MB
  MaxBackups: 100,              //最大保留日志文件数量
  MaxAge:     30,               //日志文件保留天数
  Compress:   false,            //是否压缩处理
 })
 infoFileCore := zapcore.NewCore(encoder, zapcore.NewMultiWriteSyncer(infoFileWriteSyncer, zapcore.AddSync(os.Stdout)), lowPriority) //第三个及之后的参数为写入文件的日志级别,ErrorLevel模式只记录error级别的日志
 //error文件writeSyncer
 errorFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
  Filename:   "./log/error.log"//日志文件存放目录
  MaxSize:    1,                 //文件大小限制,单位MB
  MaxBackups: 5,                 //最大保留日志文件数量
  MaxAge:     30,                //日志文件保留天数
  Compress:   false,             //是否压缩处理
 })

测试日志到达指定大小后自动会切分


alt

例如,当info级别的日志文件到达2M时,会根据当时的时间戳,切分出一个info-2023-04-13T05-27-18.296.log。 后续新写入的info级别的日志将写入到info.log,直到又到达2M,继续会切分。


测试日志到达指定最大保留日志文件数量后,将作何操作


清掉log文件夹,修改error日志配置:

 errorFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
  Filename:   "./log/error.log"//日志文件存放目录
  MaxSize:    1,                 //文件大小限制,单位MB
  MaxBackups: 3,                 //最大保留日志文件数量
  MaxAge:     30,                //日志文件保留天数
  Compress:   false,             //是否压缩处理
 })

代码中只打印error日志,执行代码 进行观察

alt

继续执行

alt

继续执行

alt
alt

可见最早拆分出的那个error-2023-04-13T05-40-48.715.log文件不见了~

继续执行,切分出来的文件数量,也会始终保持3个


完整变化图:

alt

测试压缩处理的效果


清掉log文件夹,修改error日志配置:

 errorFileWriteSyncer := zapcore.AddSync(&lumberjack.Logger{
  Filename:   "./log/error.log"//日志文件存放目录
  MaxSize:    5,                 //文件大小限制,单位MB
  MaxBackups: 10,                 //最大保留日志文件数量
  MaxAge:     30,                //日志文件保留天数
  Compress:   false,             //是否压缩处理
 })

代码中只打印error日志,执行代码,循环10000000次, 进行观察

alt

不压缩共占用814M存储空间


清掉log文件夹,修改Compress字段为true,执行代码:

alt

启用压缩后,仅占用了30M磁盘空间!

不太好的地方就是不方便直接查看了,需要解压后查看。但大大省了所占用的空间


golang zap日志库使用[2]


lumberjack这个库目前只支持按文件大小切割(按时间切割效率低且不能保证日志数据不被破坏,详情见https://github.com/natefinch/lumberjack/issues/54)

想按日期切割可以使用github.com/lestrrat-go/file-rotatelogs[3]这个库(目前不维护了)




file-rotatelogs实现按时间的切割


注意:

github.com/lestrrat-go/file-rotatelogs[4](2021年后不更新了) 和 github.com/lestrrat/go-file-rotatelogs[5](2018年以后就不更新了) 两个不一样。。前面那个是更新的,作者是一个人...

(有一个linux系统上的日志工具,也叫logrotate)

logrotate 是一个用于日志文件轮换的 Go 语言库,支持按时间轮换、按文件大小轮换和按行数轮换。还支持在轮换时压缩文件、删除旧文件、给文件添加时间戳等功能

用zap和go-file-rotatelogs实现日志的记录和日志按时间分割[6]


WithRotationCount和WithMaxAge两个选项不能共存,只能设置一个(都设置编译时不会出错,但运行时会报错。也是为了防止影响切分的处理逻辑):

panic: options MaxAge and RotationCount cannot be both set

package main

import (
 "fmt"
 "io"
 "net/http"
 "time"

 rotatelogs "github.com/lestrrat-go/file-rotatelogs"
 "go.uber.org/zap"
 "go.uber.org/zap/zapcore"
)

// 使用file-rotatelogs做切分

var sugarLogger *zap.SugaredLogger

func main() {
 fmt.Println("shuang提示:begin main")
 InitLogger()
 defer sugarLogger.Sync()

 for i := 0; i < 100000; i++ {
  simpleHttpGet("www.cnblogs.com")
  simpleHttpGet("https://www.baidu.com")
 }

}

// 例子,http访问url,返回状态
func simpleHttpGet(url string) {
 fmt.Println("begin simpleHttpGet:" + url)
 sugarLogger.Debugf("Trying to hit GET request for %s", url)
 resp, err := http.Get(url)
 if err != nil {
  sugarLogger.Errorf("Error fetching URL %s : Error = %s", url, err)
 } else {
  sugarLogger.Infof("Success! statusCode = %s for URL %s", resp.Status, url)
  resp.Body.Close()
 }
}

func InitLogger() {
 encoder := getEncoder()

 //两个interface,判断日志等级
 //warnlevel以下归到info日志
 infoLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
  return lvl < zapcore.WarnLevel
 })
 //warnlevel及以上归到warn日志
 warnLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
  return lvl >= zapcore.WarnLevel
 })

 infoWriter := getLogWriter("/Users/fliter/zap-demo/demo2-log/info")
 warnWriter := getLogWriter("/Users/fliter/zap-demo/demo2-log/warn")

 //创建zap.Core,for logger
 core := zapcore.NewTee(
  zapcore.NewCore(encoder, infoWriter, infoLevel),
  zapcore.NewCore(encoder, warnWriter, warnLevel),
 )
 //生成Logger
 logger := zap.New(core, zap.AddCaller())
 sugarLogger = logger.Sugar()
}

// getEncoder
func getEncoder() zapcore.Encoder {
 encoderConfig := zap.NewProductionEncoderConfig()
 encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
 encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
 return zapcore.NewConsoleEncoder(encoderConfig)
}

// 得到LogWriter
func getLogWriter(filePath string) zapcore.WriteSyncer {
 warnIoWriter := getWriter(filePath)
 return zapcore.AddSync(warnIoWriter)
}

// 日志文件切割
func getWriter(filename string) io.Writer {

 //保存日志30天,每1分钟分割一次日志
 hook, err := rotatelogs.New(
  filename+"_%Y-%m-%d %H:%M:%S.log",
  // 为最新的日志建立软连接,指向最新日志文件
  rotatelogs.WithLinkName(filename),

  // 清理条件: 将已切割的日志文件按条件(数量or时间)直接删除
  //--- MaxAge and RotationCount cannot be both set  两者不能同时设置
  //--- RotationCount用来设置最多切割的文件数(超过的会被 从旧到新 清理)
  //--- MaxAge 是设置文件清理前的最长保存时间 最小分钟为单位
  //--- if both are 0, give maxAge a default 7 * 24 * time.Hour
  // WithRotationCount和WithMaxAge两个选项不能共存,只能设置一个(都设置编译时不会出错,但运行时会报错。也是为了防止影响切分的处理逻辑)
  //rotatelogs.WithRotationCount(10),       // 超过这个数的文件会被清掉
  rotatelogs.WithMaxAge(time.Hour*24*30), // 保存多久(设置文件清理前的最长保存时间 最小分钟为单位)

  // 切分条件(将日志文件做切割;WithRotationTime and WithRotationSize ~~两者任意一个条件达到都会切割~~)
  // 经过亲测后发现,如果日志没有持续增加,WithRotationTime设置较小(如10s),并不会按WithRotationTime频次切分文件。当日志不停增加时,会按照WithRotationTime设置来切分(即便WithRotationTime设置的很小)
  rotatelogs.WithRotationTime(time.Second*10),     // 10秒分割一次(设置日志切割时间间隔,默认 24 * time.Hour)
  rotatelogs.WithRotationSize(int64(1*1024*1024*1024)), // 文件达到多大则进行切割,单位为 bytes;
 )
 if err != nil {
  panic(err)
 }
 return hook
}

验证其切分功能:


将触发切分的文件大小设置得很大(110241024*1024 Byte即1 GB),切分时间设置得较小(10秒分割一次),执行代码,可以观察到日志文件的变化:

alt
alt

再将触发切分的文件大小设置得很小(1102450 Byte即50 KB),切分时间设置得较大(24h分割一次),执行代码,清掉之前的日志,再观察到日志文件的变化:

alt
alt

将触发切分的文件大小设置得很小(1102435 Byte即35 KB),同时切分时间也设置得很小(10s分割一次),执行代码,清掉之前的日志,再观察到日志文件的变化:

alt

当前日志容量大于配置的容量时,会生成新的日志文件,如果时间一样,在时间后缀后面会自动加上一个数字后缀,以此区分同一时间的不同日志文件,如果时间不一样,则生成新的时间后缀文件 (golang实现分割日志[7])

日志文件中是会出现有的命中时间规则,有的命中文件大小规则的情况,两者命名格式不同,参考上图


切分之后执行压缩命令


alt

默认是没有的,不像lumberjack那样提供Compress选项

前面所提的还支持在轮换时压缩文件、删除旧文件、给文件添加时间戳等功能需要自己实现。 提供了一个WithHandler回调函数,发生切分后会触发该函数,可以在其中进项压缩等操作

alt

改一下代码(不再请求网站因为速度太慢,直接在for里面写日志)

不启用压缩:

alt

启用压缩,效果显著:

alt

相关代码:

package main

import (
 "archive/zip"
 "fmt"
 "io"
 "net/http"
 "os"
 "path/filepath"
 "reflect"
 "time"

 "github.com/davecgh/go-spew/spew"
 rotatelogs "github.com/lestrrat-go/file-rotatelogs"
 "go.uber.org/zap"
 "go.uber.org/zap/zapcore"
)

// 使用file-rotatelogs做切分

var sugarLogger *zap.SugaredLogger

func main() {
 fmt.Println("shuang提示:begin main")
 InitLogger()
 defer sugarLogger.Sync()

 for i := 0; i < 10000000; i++ {

  sugarLogger.Infof("测试压缩后少占用的空间,这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本这是填充文本,i is %d", i)

  //simpleHttpGet("www.cnblogs.com", i)
  //simpleHttpGet("https://www.baidu.com", i)
 }

 time.Sleep(10000e9)

}

// 例子,http访问url,返回状态
func simpleHttpGet(url string, i int) {
 //fmt.Println("begin simpleHttpGet:" + url)
 sugarLogger.Debugf("Trying to hit GET request for %s, i is %d", url, i)
 resp, err := http.Get(url)
 if err != nil {
  sugarLogger.Errorf("Error fetching URL %s : Error = %s, i is %d", url, err, i)
 } else {
  sugarLogger.Infof("Success! statusCode = %s for URL %s,i is %d", resp.Status, url, i)
  resp.Body.Close()
 }
}

func InitLogger() {
 encoder := getEncoder()

 //两个interface,判断日志等级
 //warnlevel以下归到info日志
 infoLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
  return lvl < zapcore.WarnLevel
 })
 //warnlevel及以上归到warn日志
 warnLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
  return lvl >= zapcore.WarnLevel
 })

 infoWriter := getLogWriter("/Users/fliter/zap-demo/demo2-log/info")
 warnWriter := getLogWriter("/Users/fliter/zap-demo/demo2-log/warn")

 //创建zap.Core,for logger
 core := zapcore.NewTee(
  zapcore.NewCore(encoder, infoWriter, infoLevel),
  zapcore.NewCore(encoder, warnWriter, warnLevel),
 )
 //生成Logger
 logger := zap.New(core, zap.AddCaller())
 sugarLogger = logger.Sugar()
}

// getEncoder
func getEncoder() zapcore.Encoder {
 encoderConfig := zap.NewProductionEncoderConfig()
 encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
 encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
 return zapcore.NewConsoleEncoder(encoderConfig)
}

// 得到LogWriter
func getLogWriter(filePath string) zapcore.WriteSyncer {
 warnIoWriter := getWriter(filePath)
 return zapcore.AddSync(warnIoWriter)
}

// 日志文件切割
func getWriter(filename string) io.Writer {

 //保存日志30天,每1分钟分割一次日志
 hook, err := rotatelogs.New(
  filename+"_%Y-%m-%d %H:%M:%S.log",
  // 为最新的日志建立软连接,指向最新日志文件
  rotatelogs.WithLinkName(filename),

  // 清理条件: 将已切割的日志文件按条件(数量or时间)直接删除
  //--- MaxAge and RotationCount cannot be both set  两者不能同时设置
  //--- RotationCount用来设置最多切割的文件数(超过的会被 从旧到新 清理)
  //--- MaxAge 是设置文件清理前的最长保存时间 最小分钟为单位
  //--- if both are 0, give maxAge a default 7 * 24 * time.Hour
  // WithRotationCount和WithMaxAge两个选项不能共存,只能设置一个(都设置编译时不会出错,但运行时会报错。也是为了防止影响切分的处理逻辑)
  //rotatelogs.WithRotationCount(10),       // 超过这个数的文件会被清掉
  rotatelogs.WithMaxAge(time.Hour*24*30), // 保存多久(设置文件清理前的最长保存时间 最小分钟为单位)

  // 切分条件(将日志文件做切割;WithRotationTime and WithRotationSize ~~两者任意一个条件达到都会切割~~)
  // 经过亲测后发现,如果日志没有持续增加,WithRotationTime设置较小(如10s),并不会按WithRotationTime频次切分文件。当日志不停增加时,会按照WithRotationTime设置来切分(即便WithRotationTime设置的很小)
  rotatelogs.WithRotationTime(time.Second*10),           // 10秒分割一次(设置日志切割时间间隔,默认 24 * time.Hour)
  rotatelogs.WithRotationSize(int64(1*1024*35000*1024)), // 文件达到多大则进行切割,单位为 bytes;

  // 其他可选配置
  //default: rotatelogs.Local ,you can set rotatelogs.UTC
  //rotatelogs.WithClock(rotatelogs.UTC),
  //rotatelogs.WithLocation(time.Local),
  //--- 当rotatelogs.New()创建的文件存在时,强制创建新的文件 命名为原文件的名称+序号,如a.log存在,则创建创建 a.log.1
  //rotatelogs.ForceNewFile(),

  rotatelogs.WithHandler(rotatelogs.Handler(rotatelogs.HandlerFunc(func(e rotatelogs.Event) {
   if e.Type() != rotatelogs.FileRotatedEventType {
    return
   }

   fmt.Println("切割完成,进行打包压缩操作")

   spew.Dump("e is:", e)

   prevFile := e.(*rotatelogs.FileRotatedEvent).PreviousFile()

   if prevFile != "" {
    // 进行压缩
    paths, fileName := filepath.Split(prevFile)
    //_ = paths
    //err := Zip("archive.zip", paths, prevFile)
    err := ZipFiles(paths+fileName+".zip", []string{prevFile})
    fmt.Println("err is", err)

    if err == nil {
     os.RemoveAll(prevFile)
    }

   }

   fmt.Println("e的类型为:", reflect.TypeOf(e))

   fmt.Println("------------------")
   fmt.Println()
   fmt.Println()
   fmt.Println()

   //ctx := CleanContext{
   // Dir:         LogsConfig.LogOutputDir,
   // DirMaxSizeG: LogsConfig.LogDirMaxSizeG,
   // DirMaxCount: LogsConfig.LogDirMaxFileCount,
   //}
   //strategyOne := CleanStrategyOne{}
   //result, err := NewCleanStrategy(&ctx, &strategyOne).
   // Clean().
   // Result()
   //Warn("文件切割,清理文件策略one已经执行完毕; 结果:%v; 错误:%v", result, err)
  }))),
 )

 if err != nil {
  panic(err)
 }
 return hook
}

// ZipFiles compresses one or many files into a single zip archive file.
// Param 1: filename is the output zip file's name.
// Param 2: files is a list of files to add to the zip.
func ZipFiles(filename string, files []string) error {

 newZipFile, err := os.Create(filename)
 if err != nil {
  return err
 }
 defer newZipFile.Close()

 zipWriter := zip.NewWriter(newZipFile)
 defer zipWriter.Close()

 // Add files to zip
 for _, file := range files {
  if err = AddFileToZip(zipWriter, file); err != nil {
   return err
  }
 }
 return nil
}

func AddFileToZip(zipWriter *zip.Writer, filename string) error {

 fileToZip, err := os.Open(filename)
 if err != nil {
  return err
 }
 defer fileToZip.Close()

 // Get the file information
 info, err := fileToZip.Stat()
 if err != nil {
  return err
 }

 header, err := zip.FileInfoHeader(info)
 if err != nil {
  return err
 }

 // Using FileInfoHeader() above only uses the basename of the file. If we want
 // to preserve the folder structure we can overwrite this with the full path.
 header.Name = filename

 // Change to deflate to gain better compression
 // see http://golang.org/pkg/archive/zip/#pkg-constants
 header.Method = zip.Deflate

 writer, err := zipWriter.CreateHeader(header)
 if err != nil {
  return err
 }
 _, err = io.Copy(writer, fileToZip)
 return err
}

//
 Zip compresses the specified files or dirs to zip archive.
 If a path is a dir don't need to specify the trailing path separator.
 For example calling Zip("archive.zip", "dir", "csv/baz.csv") will get archive.zip and the content of which is
 baz.csv
 dir
 ├── bar.txt
 └── foo.txt
 Note that if a file is a symbolic link it will be skipped.
//
 https://blog.csdn.net/K346K346/article/details/122441250
//func Zip(zipPath string, paths ...string) error {
// // Create zip file and it's parent dir.
// if err := os.MkdirAll(filepath.Dir(zipPath), os.ModePerm); err != nil {
//  return err
// }
// archive, err := os.Create(zipPath)
// if err != nil {
//  return err
// }
// defer archive.Close()
//
// // New zip writer.
// zipWriter := zip.NewWriter(archive)
// defer zipWriter.Close()
//
// // Traverse the file or directory.
// for _, rootPath := range paths {
//  // Remove the trailing path separator if path is a directory.
//  rootPath = strings.TrimSuffix(rootPath, string(os.PathSeparator))
//
//  // Visit all the files or directories in the tree.
//  err = filepath.Walk(rootPath, walkFunc(rootPath, zipWriter))
//  if err != nil {
//   return err
//  }
// }
// return nil
//}
//
//func walkFunc(rootPath string, zipWriter *zip.Writer) filepath.WalkFunc {
// return func(path string, info fs.FileInfo, err error) error {
//  if err != nil {
//   return err
//  }
//
//  // If a file is a symbolic link it will be skipped.
//  if info.Mode()&os.ModeSymlink != 0 {
//   return nil
//  }
//
//  // Create a local file header.
//  header, err := zip.FileInfoHeader(info)
//  if err != nil {
//   return err
//  }
//
//  // Set compression method.
//  header.Method = zip.Deflate
//
//  // Set relative path of a file as the header name.
//  header.Name, err = filepath.Rel(filepath.Dir(rootPath), path)
//  if err != nil {
//   return err
//  }
//  if info.IsDir() {
//   header.Name += string(os.PathSeparator)
//  }
//
//  // Create writer for the file header and save content of the file.
//  headerWriter, err := zipWriter.CreateHeader(header)
//  if err != nil {
//   return err
//  }
//  if info.IsDir() {
//   return nil
//  }
//  f, err := os.Open(path)
//  if err != nil {
//   return err
//  }
//  defer f.Close()
//  _, err = io.Copy(headerWriter, f)
//  return err
// }
//}


完整demo项目代码 以zap为例,展示如何切割日志文件。 使用Go生态两个使用最高的切分库[8]


关于压缩:

压缩解压文件[9]

Golang 学习笔记(五)- archive/zip 实现压缩及解压[10]

Golang zip 压缩与解压[11]




更多参考:

zap日志切割,同时支持按日期拆分,也支持按日志固定大小拆分,支持定时清理[12]

go-logrus 日志框架封装使用[13]

Go zap日志[14]

设计自用的golang日志模块[15]

golang log rotate file[16]

golang高性能日志库zap的使用[17]

参考资料

[1]

lumberjack: https://github.com/natefinch/lumberjack

[2]

golang zap日志库使用: https://segmentfault.com/a/1190000040443996

[3]

github.com/lestrrat-go/file-rotatelogs: https://github.com/lestrrat/go-file-rotatelogs

[4]

github.com/lestrrat-go/file-rotatelogs: https://github.com/lestrrat-go/file-rotatelogs

[5]

github.com/lestrrat/go-file-rotatelogs: https://github.com/lestrrat/go-file-rotatelogs

[6]

用zap和go-file-rotatelogs实现日志的记录和日志按时间分割: https://blog.csdn.net/weixin_43881017/article/details/110200176

[7]

golang实现分割日志: https://blog.csdn.net/qq_42119514/article/details/121372416

[8]

以zap为例,展示如何切割日志文件。 使用Go生态两个使用最高的切分库: https://github.com/cuishuang/zap-demo/tree/main

[9]

压缩解压文件: https://www.topgoer.com/%E5%85%B6%E4%BB%96/%E5%8E%8B%E7%BC%A9%E8%A7%A3%E5%8E%8B%E6%96%87%E4%BB%B6.html

[10]

Golang 学习笔记(五)- archive/zip 实现压缩及解压: https://learnku.com/articles/23434/golang-learning-notes-five-archivezip-to-achieve-compression-and-decompression

[11]

Golang zip 压缩与解压: https://blog.csdn.net/K346K346/article/details/122441250

[12]

zap日志切割,同时支持按日期拆分,也支持按日志固定大小拆分,支持定时清理: https://blog.csdn.net/qq_22186119/article/details/122003691

[13]

go-logrus 日志框架封装使用: https://www.jianshu.com/p/722250f0b609

[14]

Go zap日志: https://blog.csdn.net/qq_41004932/article/details/119760061

[15]

设计自用的golang日志模块: https://studygolang.com/articles/12537

[16]

golang log rotate file: https://juejin.cn/s/golang%20log%20rotate%20file

[17]

golang高性能日志库zap的使用: https://www.jianshu.com/p/910b626f67d9

本文由 mdnice 多平台发布

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java版工程行业管理系统源码-专业的工程管理软件-em提供一站式服务 em

​ Java版工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离 功能清单如下&#xff1a; 首页 工作台&#xff1a;待办工作、消息通知、预警信息&#xff0c;点击可进入相应的列表 项目进度图表&#xff1a;选择&#xff08;总体或单个&#xff09;项目…

弱监督语义分割伪标签可视化(把单通道灰度图转为voc格式语义分割标签的彩色形式)

一、目的 以图片2007_001960为例&#xff0c;voc数据集中的原图和对应的语义分割标签分别如下&#xff1a; 图1 图2 图像级标签WSSS任务第一阶段最后生成的pseudo mask如下&#xff1a; 图3 我们的…

【雕爷学编程】MicroPython动手做(33)——物联网之天气预报3

天气&#xff08;自然现象&#xff09; 是指某一个地区距离地表较近的大气层在短时间内的具体状态。而天气现象则是指发生在大气中的各种自然现象&#xff0c;即某瞬时内大气中各种气象要素&#xff08;如气温、气压、湿度、风、云、雾、雨、闪、雪、霜、雷、雹、霾等&#xff…

react中PureComponent的理解与使用

一、作用 它是一个纯组件&#xff0c;会做一个数据的浅比较&#xff0c;当props和state没改变的时候&#xff0c;不会render重新渲染&#xff0c; 改变后才会render重新渲染&#xff0c;提高性能。 二、使用 三、注意 它不能和shouldComponentUpdate生命周期同时使用。因为它…

如何获取最新的底图边线数据(高德)

由于近期的大屏项目需要地图的边界线的数据&#xff0c;找了很多方式&#xff0c;都有局限性&#xff0c;就是不能保证是最新的&#xff0c;所以使用高德地图提供的边线数据&#xff0c;那就肯定是最新的了&#xff0c;之前仔细看文档&#xff0c;现在仔细看了&#xff0c;才发…

【云原生】k8s中Contrainer 生命周期回调/策略/指针学习

个人主页&#xff1a;征服bug-CSDN博客 kubernetes专栏&#xff1a;kubernetes_征服bug的博客-CSDN博客 目录 1 容器生命周期 2 容器生命周期回调/事件/钩子 3 容器重启策略 4 自定义容器启动命令 5 容器探针 1 容器生命周期 Kubernetes 会跟踪 Pod 中每个容器的状态&am…

Python 批量处理JSON文件,替换某个值

Python 批量处理JSON文件&#xff0c;替换某个值 直接上代码&#xff0c;替换key TranCode的值 New 为 Update。输出 cancel忽略 import json import os import iopath D:\\Asics\\850\\202307 # old path2 D:\\test2 # new dirs os.listdir(path) num_flag 0 for file…

【Spring练习项目】博客系统

目录 1.项目展示2.项目结构设计3.项目功能设计4 数据库准备4.1 建表4.2 DB相关数据 5.项目模块6.添加项目公共模块6.1 common6.2 实现前端界面 7.功能实现7.1实现博客列表约定前后端交互接口实现服务器代码实现客户端代码 7.2实现博客详情约定前后端交互接口实现服务器代码实现…

基于图片、无人机、摄像头拍摄进行智能检测功能

根据要求进行无人机拍摄的视频或图片进行智能识别&#xff0c;开发过程需要事项 1、根据图片案例进行标记&#xff0c;进行模型训练 2、视频模型训练 开发语言为python 根据需求功能进行测试结果如下 根据车辆识别标记进行的测试结果截图 测经过查看视频 8月1日

opencv-33 图像平滑处理-中值滤波cv2.medianBlur()

中值滤波是一种常见的图像处理滤波技术&#xff0c;用于去除图像中的噪声。它的原理是用一个滑动窗口&#xff08;也称为卷积核&#xff09;在图像上移动&#xff0c;对窗口中的像素值进行排序&#xff0c;然后用窗口中像素值的中值来替换中心像素的值。这样&#xff0c;中值滤…

【二等奖方案】Web攻击检测与分类识别赛题「机器学习」团队解题思路

2022 CCF BDCI 数字安全公开赛 赛题「Web攻击检测与分类识别」 地址&#xff1a;http://go.datafountain.cn/4Zj 机器学习战队 获奖方案 团队简介 我们团队由五名成员组成&#xff0c;对机器学习都非常感兴趣&#xff0c;同时在机器学习领域有着丰富的实战经验&#xff0c…

LeetCode 42. 接雨水(动态规划 / 单调栈)

题目&#xff1a; 链接&#xff1a;LeetCode 42. 接雨水 难度&#xff1a;困难 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 示例 1&#xff1a; 输入&#xff1a;height [0,1,0,2,1,0,1,3,2,1,2…

爆肝整理,Postman接口测试-参数关联实战(详细步骤)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 接口测试什么时候…

ES6及以上新特性

ES6&#xff08;ECMAScript 2015&#xff09;及以上版本引入了许多新特性&#xff0c;每个版本都有不同的增强和改进。以下是 ES6 及以上版本的新特性的详细描述&#xff1a; ES6&#xff08;ECMAScript 2015&#xff09;&#xff1a; let 和 const 声明&#xff1a;引入块级作…

瑞吉外卖实战-笔记

软件开发的流程 角色分工 软件环境 开发环境的搭建 数据库环境 maven环境 1.创建完成后&#xff0c;需要检查一下编码、maven仓库、jdk等 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</a…

pytorch 中 view 和reshape的区别

在 PyTorch&#xff08;一个流行的深度学习框架&#xff09;中&#xff0c; reshape 和 view 都是用于改变张量&#xff08;tensor&#xff09;形状的方法&#xff0c;但它们在实现方式和使用上有一些区别。下面是它们之间的主要区别&#xff1a; 实现方式&#xff1a; reshap…

机器学习--课后作业--hw1

机器学习(课后作业–hw1) 本篇文章全文参考这篇blog 网上找了很多教程&#xff0c;这个是相对来说清楚的&#xff0c;代码可能是一模一样&#xff0c;只是进行了一些微调&#xff0c;但是一定要理解这个模型具体的处理方法&#xff0c;这个模型我认为最巧妙的它对于数据的处理…

HTTP(超文本传输协议)学习

关于HTTP补学 一、HTTP能干什么 通过下图能够直观的看出&#xff1a;“交换数据 ” 二、HTTP请求例子 一个 HTTP 方法&#xff0c;通常是由一个动词&#xff0c;像 GET、POST 等&#xff0c;或者一个名词&#xff0c;像 OPTIONS、HEAD 等&#xff0c;来定义客户端执行的动作。…

Django之JWT库与SimpleJWT库的使用

Django之JWT库与SimpleJWT库的使用 JWTJWT概述头部(header)载荷(payload)签名(signature) Django使用JWT说明jwt库的使用安装依赖库配置settings.py文件配置urls.py文件创建视图配置权限 SimpleJWT库的使用安装SimpleJWT库配置Django项目配置路由创建用户接口测试身份认证自定义…

MP的开发流程-2

RESTful的实现等级 0级&#xff1a;传统的RPC&#xff0c;基于SOAP的WS&#xff0c;调用的服务名&#xff0c;参数放在HTTP协议的body里面&#xff0c;同时必须以POST方式提交&#xff0c;问题在于你必须清楚的知道所有服务&#xff0c;子服务&#xff0c;及其参数的信息&…