自定义工具包:加快项目开发速度

​ 这篇文章,我打算自己做一个自己常用的一些 Go 语言的通用工具包,比如 MySQL、Redis 的连接,日志配置等。

MySQL

启动 MySQL 服务

​ 我们先用 docker 起一个 MySQL 服务,便于我们后面的连接。

​ 用 docker 启动一个 MySQL 容器:

docker run --name go_mysql -e MYSQL_ROOT_PASSWORD=root -d -p 8086:3306 mysql:8.0

​ 登入容器内的 MySQL:

docker run -it --network=host --name=ft  mysql:8.0 mysql -h 0.0.0.0  -P 8086 -u root -p

​ 这样,我们的 MySQL 服务就启动了。

​ 以后每次重新进入该容器,可以使用下述命令:

docker start -i ft

​ 我们在数据库中创建一个用户表,用于后面和数据库进行交互操作:

create database user;
use user;create table if not exists t_user (id int not null auto_increment,name varchar(103) not null,age int not null,gender varchar(30) not null,password varchar(255) not null default '',nickname varchar(100) not null default '',create_time timestamp null default current_timestamp comment '创建时间',creator varchar(100) not null default '',modify_time timestamp null default current_timestamp on update current_timestamp,modifier varchar(188) not null default '',primary key (id)
);

​ 这里我们还需要注意的一个点,就是需要将 服务器对应的端口进行开放,不然本地的项目无法连接上服务器上的数据库,由于我们这里将服务器上的 8086 端口映射到容器内的 3306 端口,所以我们需要将服务器的 8086 端口对外开放。

使用 Viper 进行项目配置

​ 详细配置可以查看官网。

使用 gorm 框架进行数据库连接

​ 详情可以查看官网。

编写配置信息文件

​ 这里使用 yml 格式:

# Config.yml
db:host: "101.200.158.100"port: 8086user: "root"password: "root"dbname: "user"max_idle_conn: 5max_open_conn: 20max_idle_time: 300
全局配置文件包
// config.go
package configimport ("fmt""github.com/spf13/viper""sync""time"
)var (config GlobalConfig // 全局配置文件once   sync.Once
)// GetGlobalConf 全局配置文件构造函数
func GetGlobalConf() *GlobalConfig {once.Do(readConf)return &config
}type DbConf struct {Host        string        `yaml:"host" mapstructure:"host"`                   // 主机号Port        string        `yaml:"port" mapstructure:"port"`                   // 端口号User        string        `yaml:"user" mapstructure:"user"`                   // 用户Password    string        `yaml:"password" mapstructure:"password"`           // 密码Dbname      string        `yaml:"Dbname" mapstructure:"Dbname"`               // 数据库名MaxIdleConn int           `yaml:"max_idle_conn" mapstructure:"max_idle_conn"` // 最大空闲连接数MaxOpenConn int           `yaml:"max_open_conn" mapstructure:"max_open_conn"` // 最大连接数MaxIdleTime time.Duration `yaml:"max_idle_time" mapstructure:"max_idle_time"` // 最大空闲时间
}type GlobalConfig struct {DbConf DbConf `yaml:"db" mapstructure:"db"` // 数据库配置
}// 将配置文件中的信息 加载到 全局配置信息结构体中
func readConf() {viper.SetConfigName("Config")   // 配置文件名viper.SetConfigType("yml")      // 配置文件类型viper.AddConfigPath("./config") // 配置文件路径viper.AddConfigPath("../config")err := viper.ReadInConfig() // 读取配置信息if err != nil {panic("read config file err:" + err.Error())}err = viper.Unmarshal(&config) // 将配置文件中的信息反序列化到结构体中if err != nil {panic("config file unmarshal err:" + err.Error())}// TODO: 这里需要用日志打印fmt.Printf("%+v\n", config)// 热更新viper.WatchConfig()viper.OnConfigChange(func(e fsnotify.Event) {// 配置文件发生变更之后会调用的回调函数readConf()fmt.Println("Config file changed:", e.Name)})
}// InitConfig TODO: 初始化日志
func InitConfig() {globalConf := GetGlobalConf()fmt.Println("GlobalConf:", globalConf)
}
MySQL 数据库连接文件
// db.go
package utilimport ("Config/config""fmt""gorm.io/driver/mysql""gorm.io/gorm""sync"
)var (db     *gorm.DBdbOnce sync.Once
)// 连接数据库
func openDB() {mysqlConf := config.GetGlobalConf().DbConf// 连接语句connArgs := fmt.Sprintf("%s:%s@(%s:%s)/%s?charset=utf8&parseTime=True&loc=Local",mysqlConf.User, mysqlConf.Password, mysqlConf.Host, mysqlConf.Port, mysqlConf.Dbname)fmt.Println("mdb addr: " + connArgs)var err errordb, err = gorm.Open(mysql.Open(connArgs), &gorm.Config{}) // 使用默认配置连接数据库if err != nil {panic("failed to connect database")}// 获取底层 sql.Db 连接对象sqlDB, err := db.DB()if err != nil {panic("fetch db connection err:" + err.Error())}sqlDB.SetMaxOpenConns(mysqlConf.MaxOpenConn)sqlDB.SetMaxIdleConns(mysqlConf.MaxIdleConn)sqlDB.SetConnMaxLifetime(mysqlConf.MaxIdleTime)
}func GetDB() *gorm.DB {dbOnce.Do(openDB)return db
}
测试模型和测试代码

​ 这里用一个用户信息来进行测试:

// model.go
package modelimport "time"// CreateModel 内嵌 model
type CreateModel struct {Creator    string    `gorm:"type:varchar(100);not null;default ''"`CreateTime time.Time `gorm:"autoCreateTime"` // 在创建时自动生成时间
}// ModifyModel 内嵌 model
type ModifyModel struct {Modifier   string    `gorm:"type:varchar(100);not null;default ''"`ModifyTime time.Time `gorm:"autoUpdateTime"` // 在更新记录时自动生成时间
}type User struct {CreateModelModifyModelID       int    `gorm:"column:id"`Name     string `gorm:"column:name"`Gender   string `gorm:"column:gender"`Age      int    `gorm:"column:age"`PassWord string `gorm:"column:password"`NickName string `gorm:"column:nickname"`
}// TableName 需要使用钩子函数指定数据库表名
func (t *User) TableName() string {return "t_user"
}

​ 这里用单元测试测试 5 个功能,分别是连接、增加、修改、删除、查询:

package utilimport ("Config/config""Config/model""fmt""os""testing""time"
)func TestMain(m *testing.M) {config.InitConfig()os.Exit(m.Run())
}func TestGetDB(t *testing.T) {db := GetDB()if db == nil {t.Errorf("database connection failed")}
}func TestInsert(t *testing.T) {db := GetDB()user := model.User{CreateModel: model.CreateModel{Creator:    "admin", // 请替换为实际的创建者CreateTime: time.Now(),},ModifyModel: model.ModifyModel{Modifier:   "admin", // 请替换为实际的修改者ModifyTime: time.Now(),},ID:       1,Name:     "ft",Gender:   "Male",Age:      25,PassWord: "password123",NickName: "johnny",}if err := db.Create(user).Error; err != nil {t.Errorf("createUser failed: %v", err)}
}func TestSelect(t *testing.T) {db := GetDB()message := &model.User{}if err := db.Where("name=?", "ft").First(message).Error; err != nil {t.Error("database name=ft is not exits")} else {fmt.Printf("%+v\n", message)}
}func TestUpdate(t *testing.T) {db := GetDB()if err := db.Model(model.User{}).Where("name=?", "ft").Update("name", "fengtao").Error; err != nil {t.Error("update user failed")}
}func TestDelete(t *testing.T) {db := GetDB()if err := db.Where("name=?", "fengtao").Delete(&model.User{}).Error; err != nil {t.Error("delete user failed")}
}
main 主函数
package mainimport "Config/config"// Init 初始化配置
func Init() {config.InitConfig()
}func main() {Init()
}

Redis

启动 Redis 服务

​ 用下面的命令启动一个 Redis 服务:

docker run --name go_redis -d -p 8089:6379 redis:6.2-rc2

​ 然后用下面的命令在 Redis 容器中启动 redis-cli,连接到 Redis 服务器:

docker exec -it b8f50ba94db0 redis-cli -h 0.0.0.0 -p 6379

​ 或者通过连接主机上的 8089 端口,因为刚刚做了映射,所以这其实相当于是访问同一个 Redis 服务器:

docker run -it --network=host --name=ft_redis redis:6.2-rc2 redis-cli -h 0.0.0.0 -p 8089

:::warning

​ 同时,也不要忘记将服务器中对应的 8089 和 6379 端口开发,否则是无法连接上的。

:::

安装 Redis

​ 使用下面的命令将 Redis 安装到项目中:

go get "github.com/redis/go-redis/v9"
编写配置信息文件
// Config.yml
redis:rhost: "101.200.158.100"rport: 8089 # 6379rdb: 0passwd: ''poolsize: 100
全局配置文件包
// config.go
type RedisConf struct {Host     string `yaml:"rhost" mapstructure:"rhost"`Port     int    `yaml:"rport" mapstructure:"rport"`DB       int    `yaml:"rdb" mapstructure:"rdb"`Password string `yaml:"passwd" mapstructure:"passwd"`PoolSize int    `yaml:"poolsize" mapstructure:"poolsize"`
}type GlobalConfig struct {DbConf    DbConf    `yaml:"db" mapstructure:"db"` // 数据库配置RedisConf RedisConf `yaml:"redis" mapstructure:"redis"`
}
Redis 数据库连接文件
package utilimport ("Config/config""context""fmt""github.com/redis/go-redis/v9""sync"
)var (redisConn *redis.ClientredisOnce sync.Once
)func initRedis() {redisConfig := config.GetGlobalConf().RedisConffmt.Printf("redisConfig ====== %+v\n", redisConfig)addr := fmt.Sprintf("%s:%d", redisConfig.Host, redisConfig.Port)redisConn = redis.NewClient(&redis.Options{Addr:     addr,Password: redisConfig.Password,DB:       redisConfig.DB,PoolSize: redisConfig.PoolSize,})if redisConn == nil {panic("failed to call redis.NewClient")}res, err := redisConn.Set(context.Background(), "abc", 100, 60).Result()fmt.Printf("res ======== %v, err ======= %v\n", res, err)_, err = redisConn.Ping(context.Background()).Result()if err != nil {fmt.Printf("Failed to ping redis, err:%s\n", err)}
}func GetRedisCli() *redis.Client {redisOnce.Do(initRedis)return redisConn
}
测试代码
package utilimport ("context""testing""time"
)func TestGetRedisCli(t *testing.T) {redis := GetRedisCli()if redis == nil {t.Errorf("Failed Redis database connection\n")} else {t.Log("success connected to Redis.")}
}func TestRedisAdd(t *testing.T) {redis := GetRedisCli()if redis == nil {t.Errorf("Failed Redis database connection\n")}_, err := redis.Set(context.Background(), "ft", "18", 60*time.Second).Result()if err != nil {t.Errorf("failed to add a key-value to redis, err:%s\n", err)}value, err := redis.Get(context.Background(), "ft").Result()if err != nil {t.Errorf("failed to add a key-value to redis, err:%s\n", err)}if value == "18" {t.Log("success quart key-value")} else if value == "" {t.Log("key-value expired")}
}func TestRedisDel(t *testing.T) {redis := GetRedisCli()if redis == nil {t.Errorf("Failed Redis database connection\n")}_, err := redis.Del(context.Background(), "ft").Result()if err != nil {t.Errorf("failed del key, err:%s\n", err)} else {t.Log("success del key")}
}

Log

安装 zap 日志库

​ 我们这里基于 Zap 实现在用 Gin 框架开发中常用的两个中间件:Logger() 和 Recovery(),这样我们就可以使用我们的日志库来接收 gin 框架默认输出的日志了。

go get -u go.uber.org/zap	// zap 日志库
go get -u github.com/gin-gonic/gin // gin 框架
go get github.com/natefinch/lumberjack // 滚动日志库
编写配置信息文件
// Config.yml
Log:level: "debug"# log_path: "" # 这是相对于单元测试的路径filename: "../log/Config/Config.log"max_size: 200max_age: 30max_backups: 7
全局配置文件包
// Config.go
type LogConfig struct {Level    string `yaml:"level" mapstructure:"level"`Filename string `yaml:"filename" mapstructure:"filename"`//LogPath    string `yaml:"log_path" mapstructure:"log_path"`MaxSize    int `yaml:"maxsize" mapstructure:"maxsize"`MaxAge     int `yaml:"max_age" mapstructure:"max_age"`MaxBackups int `yaml:"max_backups" mapstructure:"max_backups"`
}
自定义日志工具包
package utilimport ("Config/config""net""net/http""net/http/httputil""os""runtime/debug""strings""time""github.com/gin-gonic/gin""github.com/natefinch/lumberjack""go.uber.org/zap""go.uber.org/zap/zapcore"
)var lg *zap.Logger// InitLogger 初始化Logger
func InitLogger(cfg *config.LogConfig) (err error) {// 获取用于日志轮换的WriteSyncerwriteSyncer := getLogWriter(cfg.Filename, cfg.MaxSize, cfg.MaxBackups, cfg.MaxAge)// 获取日志编码器encoder := getEncoder()// 解析日志级别var l = new(zapcore.Level)err = l.UnmarshalText([]byte(cfg.Level))if err != nil {return}// 创建新的zap Logger核心core := zapcore.NewCore(encoder, writeSyncer, l)// 创建新的zap Logger实例,并添加调用者信息lg = zap.New(core, zap.AddCaller())// 替换zap包中全局的logger实例,后续在其他包中只需使用zap.L()调用即可zap.ReplaceGlobals(lg)return
}// getEncoder 返回zapcore.Encoder实例
func getEncoder() zapcore.Encoder {// JSON编码器的配置encoderConfig := zap.NewProductionEncoderConfig()encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoderencoderConfig.TimeKey = "time"encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoderencoderConfig.EncodeDuration = zapcore.SecondsDurationEncoderencoderConfig.EncodeCaller = zapcore.ShortCallerEncoderreturn zapcore.NewJSONEncoder(encoderConfig)
}// getLogWriter 返回zapcore.WriteSyncer实例,使用lumberjack.Logger作为日志轮换的底层日志记录器
func getLogWriter(filename string, maxSize, maxBackup, maxAge int) zapcore.WriteSyncer {lumberJackLogger := &lumberjack.Logger{Filename:   filename,MaxSize:    maxSize,MaxBackups: maxBackup,MaxAge:     maxAge,}return zapcore.AddSync(lumberJackLogger)
}// GinLogger 接收gin框架默认的日志
func GinLogger() gin.HandlerFunc {return func(c *gin.Context) {start := time.Now()path := c.Request.URL.Pathquery := c.Request.URL.RawQueryc.Next()cost := time.Since(start)// 使用zap.Info记录信息lg.Info(path,zap.Int("status", c.Writer.Status()),zap.String("method", c.Request.Method),zap.String("path", path),zap.String("query", query),zap.String("ip", c.ClientIP()),zap.String("user-agent", c.Request.UserAgent()),zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),zap.Duration("cost", cost),)}
}// GinRecovery 恢复项目可能出现的panic,并使用zap记录相关信息
func GinRecovery(stack bool) gin.HandlerFunc {return func(c *gin.Context) {defer func() {if err := recover(); err != nil {// 检查是否是断开的连接,这不需要panic堆栈跟踪。var brokenPipe boolif ne, ok := err.(*net.OpError); ok {if se, ok := ne.Err.(*os.SyscallError); ok {if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {brokenPipe = true}}}httpRequest, _ := httputil.DumpRequest(c.Request, false)if brokenPipe {// 使用zap.Error记录错误信息lg.Error(c.Request.URL.Path,zap.Any("error", err),zap.String("request", string(httpRequest)),)// 如果连接已断开,我们无法向其写入状态。c.Error(err.(error)) // nolint: errcheckc.Abort()return}if stack {// 使用zap.Error记录带有堆栈跟踪的错误信息lg.Error("[Recovery from panic]",zap.Any("error", err),zap.String("request", string(httpRequest)),zap.String("stack", string(debug.Stack())),)} else {// 使用zap.Error记录不带堆栈跟踪的错误信息lg.Error("[Recovery from panic]",zap.Any("error", err),zap.String("request", string(httpRequest)),)}c.AbortWithStatus(http.StatusInternalServerError)}}()c.Next()}
}

​ 在使用时,需要注意,先使用初始化函数 InitLogger 将配置文件中的信息读取出来。

测试代码
package utilimport ("Config/config""github.com/gin-gonic/gin""go.uber.org/zap""testing"
)func TestLog(t *testing.T) {if err := InitLogger(&config.GetGlobalConf().LogConfig); err != nil {t.Errorf("Failed to initialize logger: " + err.Error())}r := gin.New()r.Use(GinLogger(), GinRecovery(true))r.GET("/", func(c *gin.Context) {c.JSON(200, gin.H{"name": "ft","age":  19,})})if err := r.Run(":8080"); err != nil {zap.L().Fatal("failed to start server, err:", zap.Error(err))}
}

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

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

相关文章

SSL证书自动化管理有什么好处?如何实现SSL证书自动化?

SSL证书是用于加密网站与用户之间传输数据的关键元素,在维护网络安全方面,管理SSL证书与部署SSL证书一样重要。定期更新、监测和更换SSL证书,可以确保网站的安全性和合规性。而自动化管理可以为此节省时间,并避免人为错误和不必要…

微信原生小程序上传与识别以及监听多个checkbox事件打开pdf

1.点击上传并识别 组件样式<van-field border"{{ false }}" placeholder"请输入银行卡卡号" model:value"{{bankNo}}" label"卡号"><van-icon bindtap"handleChooseImg" slot"right-icon" name"sca…

IDEA中启动项目报堆内存溢出或者没有足够内存的错误

1.报错现象 java.lang.OutOfMemoryError: Java heap space 或者 Could not reserve enough space for object heap 2.解决办法 在运行配置中VM选项后加下面的配置&#xff1a; -server -XX:MaxHeapSize256m -Xms512m -Xmx512m -XX:PermSize128M -XX:MaxPermSize256m 3.JVM虚…

单表查询练习

目录 题目&#xff1a; 制定约束&#xff1a; 添加表格信息&#xff1a; 所需查询的信息&#xff1a; 实验步骤&#xff1a; 第一步&#xff1a;制作表格 创建新的数据库 创建表格约束&#xff1a; 为表格加入数据&#xff1a; 第二步&#xff1a;查询信息 题目&…

Red Hat Enterprise Linux 7.9 安装图解

引导和开始安装 选择倒计时结束前&#xff0c;通过键盘上下键选择下图框选项&#xff0c;启动图形化安装过程。 安装语言选择 这里要区分一下&#xff0c;当前选中的语言作为安装过程中安装器所使用的语言&#xff0c;这里我们选择中文简体。不过细心的同学可能发现&#xff0c…

使用Mybatis-plus进行分页查询

MyBatis-Plus 是一个 MyBatis 的增强工具&#xff0c;在 MyBatis 基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。它提供了强大的分页功能&#xff0c;非常方便进行分页查询。下面是一个具体的例子&#xff0c;展示了如何使用 MyBatis-Plus 进行分页查询&#x…

OpenGL ES之深入解析如何绘制“跳动的心“特效

最近在浏览技术网站时,偶然间发现如下这个"跳动的心"特效,觉得蛮好玩的,当得知这个特效是用纯代码实现(GLSL 实现)的,确实又被惊到:追溯该特效最初的来源,最终在 ShaderToy 网站看到它的原始实现,另外在 ShaderToy 上还发现了无数类似惊人的特效,并且这些特…

JVM:性能监控工具分析和线上问题排查实践

前言 在日常开发过程中&#xff0c;多少都会碰到一些jvm相关的问题&#xff0c;比如&#xff1a;内存溢出、内存泄漏、cpu利用率飙升到100%、线程死锁、应用异常宕机等。 在这个日益内卷的环境&#xff0c;如何运用好工具分析jvm问题&#xff0c;成为每个java攻城狮必备的技能…

第8章 通信网络安全

文章目录 一、信息系统安全概述 1.信息系统的构成和分类 信息系统是将用于收集、处理、存储和传播信息的部件组织在一起而成的相关联的整体&#xff0c;般是由计算机硬件、网络和通信设备、计算机软件、信息资源和信息用户组成。它是以处理信息流为目的的人机一体化系统。信息系…

小程序进阶学习(视频完结)(核心,重点)

首先上面是一个视频播放器 把视频的宽度设置为100%即可铺满全屏 然后视频的标题和作者 最后就是一个视频播放列表 &#xff0c;设置一个固定位置开始滚动即可 还有一个问题没有解决&#xff0c;大家出出主意。 在播放页面在点击一个新的视频去播放&#xff0c;点进去的新视频获…

解决没有进入docker的权限问题

原因&#xff1a; 在Docker默认情况下只有root用户或者在docker组中的用户才有权限访问Docker服务 解决方法&#xff1a; su - root usermod -aG docker username参考链接&#xff1a;解决没有足够权限访问Docker守护进程的问题permission denied while trying to connect to …

C/C++ BM6判断链表中是否有环

文章目录 前言题目解决方案一1.1 思路阐述1.2 源码 解决方案二2.1 思路阐述2.2 源码 总结 前言 做了一堆单链表单指针的题目&#xff0c;这次是个双指针题&#xff0c;这里双指针的作用非常明显。 题目 判断给定的链表中是否有环。如果有环则返回true&#xff0c;否则返回fal…

计算机网络-ACL实验

一、NAT实验配置 NAT实验配置 通过基本ACL匹配VLAN 10网段&#xff0c;然后在出口设备NAT转换只要匹配到VLAN10地址则进行转换。 核心交换机 # 配置VLAN和默认路由&#xff0c;配置Trunk和Access接口 interface Vlanif10ip address 192.168.10.254 255.255.255.0 # interface V…

深入了解WPF控件:基础属性与用法(五)

掌握WPF控件&#xff1a;熟练常用属性&#xff08;五&#xff09; Image 是一种在WPF应用程序中显示图片的方式。它可以用于显示静态图片&#xff0c;也可以用于显示动态图片&#xff0c;如GIF。此外&#xff0c;Image控件还可以自适应大小&#xff0c;根据容器的大小自动调整…

关于gltf模型格式文件的学习

目录 glTF模型 小黄鸭的gltf模型 字段分析 scene nodes meshes primitives attributes indices mode material accessors bufferView byteOffset count componentType type materials textures images samplers magFilter与minFilter wrapS与wrapT 进行…

高效火情监测,科技助力森林防火【数字地球开放平台】

数字地球开放平台-以卫星遥感为核心的空天信息服务开放平台 (geovisearth.com) 2019年3月30日&#xff0c;四川省凉山州木里县爆发了一场森林火灾&#xff0c;火点位于海拔3800米左右&#xff0c;地形险峻、坡度陡峭、谷深难以抵挡火势。在扑救的过程中&#xff0c;27名森林消防…

最小公倍数之和(莫比乌斯反演P3911)

路径&#xff1a; P3911 最小公倍数之和 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 思路&#xff1a; 代码&#xff1a; #define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<string> #include<cstring> #include<cmath> #include<…

JavaSE 万字总结知识点(期末复习指南)

目录 一.Java的特性 二.注释 三.数据类型 基本数据类型 包装类 引用数据类型 四.运算符 五.逻辑控制 选择语句 循环语句 六.数组 七.方法 八.类与对象 构造方法 内部类 九.继承和多态 十.抽象类与接口 抽象类 接口 十一.异常 一.Java的特性 Java最初由Sun…

2401vim,vim实现任务列表

功能很简单,就是回车执行,F8切换背景颜色. 主要在标号.vim里面实现,用了几个变量: let s:bh1 let s:wj"" let s:zd{} let s:zd2{}利用标号来高亮行.在vimrc中加入 au BufWinEnter *.rw call Chbh() au BufWinLeave *.rw call Bcbh()进入与离开缓冲时执行. 在E:\Vim…

助力焊接场景下自动化缺陷检测识别,基于YOLOv5【n/s/m/l/x】全系列参数模型开发构建工业焊接场景下缺陷检测识别分析系统

焊接是一个不陌生但是对于开发来说相对小众的场景&#xff0c;在我们前面的博文开发实践中也有一些相关的实践&#xff0c;感兴趣的话可以自行移步阅读即可&#xff1a; 《轻量级模型YOLOv5-Lite基于自己的数据集【焊接质量检测】从零构建模型超详细教程》 《基于DeepLabV3Pl…