Go 项目依赖注入wire工具最佳实践介绍与使用

news2024/11/29 10:31:35

文章目录

    • 一、引入
    • 二、控制反转与依赖注入
    • 三、为什么需要依赖注入工具
      • 3.1 示例
      • 3.2 依赖注入写法与非依赖注入写法
    • 四、wire 工具介绍与安装
      • 4.1 wire 基本介绍
      • 4.2 安装
    • 五、Wire 的基本使用
      • 5.1 前置代码准备
      • 5.2 使用 Wire 工具生成代码
    • 六、Wire 核心技术
      • 5.1 抽象语法树分析
      • 5.2 模板编程
    • 七、Wire 的核心概念
      • 7.1 两个核心概念
      • 7.2 Wire 提供者(providers)
      • 7.3 Wire 注入器(injectors)
    • 八、Wire 的高级用法
      • 8.1 绑定接口
      • 8.2 结构体提供者(Struct Providers)
      • 8.3 绑定值
      • 8.4 使用结构体字段作为提供者(providers)
      • 8.5 清理函数
      • 8.6 备用注入器语法
    • 九、参考文档

一、引入

在Go语言的项目开发中,为了提高代码的可测试性和可维护性,我们通常会采用依赖注入(Dependency Injection,简称DI)的设计模式。依赖注入可以让高层模块不依赖底层模块的具体实现,而是通过抽象来互相依赖,从而使得模块之间的耦合度降低,系统的灵活性和可扩展性增强。

二、控制反转与依赖注入

控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI)。依赖注入是生成灵活和松散耦合代码的标准技术,通过明确地向组件提供它们所需要的所有依赖关系。在 Go 中通常采用将依赖项作为参数传递给构造函数的形式:

构造函数NewUserRepository在创建UserRepository时需要从外部将依赖项db作为参数传入,我们在UserRepository中无需关注db的创建逻辑,实现了代码解耦。

// NewUserRepository 创建BookRepo的构造函数
func NewUserRepository(db *gorm.DB) *UserRepository {
	return &UserRepository{db: db}
}

区别于控制反转,如果在NewUserRepository函数中自行创建相关依赖,这将导致代码高度耦合并且难以维护和调试。

// NewUserRepository 创建UserRepository的构造函数
func NewUserRepository() *UserRepository {
  db, _ := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{})
	return &UserRepository{db: db}
}

三、为什么需要依赖注入工具

3.1 示例

如果上面示例代码不够清晰的话,我们来看这两段代码:

// NewUserRepositoryV1非依赖注入的写法
func NewUserRepositoryV1(dbCfg DBConfig, c CacheConfig)*UserRepository{
    db, err := gorm.Open(mysql.Open(dbcfg.DSN))
    if err != nil {
        panic(err)
    }
    ud = dao.NewUserDAO(db)
    uc = cache.NewUserCache(redis.NewClient(&redis.Options{
            Addr: c.Addr,
    }))
    return &UserRepository{
        dao: ud,
        cache: uc,
    }
}

// NewUserRepository 依赖注入的写法
func NewUserRepository(d *dao.UserDAO, c *cache.UserCache)*UserRepository{
    return &UserRepository{
        dao: d,
        cache: c,
    }
}

可以清楚地看到,这两段代码展示了在Go语言中实现依赖注入的两种不同方式。
第一段代码 NewUserRepositoryV1 是非依赖注入的写法。在这个函数中,UserRepository 的依赖(dbcache)是在函数内部创建的。这种方式的问题在于,它违反了单一职责原则,因为 NewUserRepositoryV1 不仅负责创建 UserRepository 实例,还负责创建其依赖的数据库和缓存客户端。这样做会导致代码耦合度较高,难以测试和维护。
第二段代码 NewUserRepository 是依赖注入的写法。这个函数接受 UserRepository 的依赖(*dao.UserDAO*cache.UserCache)作为参数,而不是在函数内部创建它们。这种方式使得 UserRepository 的创建与它的依赖解耦,更容易测试,因为你可以轻松地为 UserRepository 提供模拟的依赖项。此外,这种写法也更符合依赖注入的原则,因为它将控制反转给了调用者,由调用者来决定 UserRepository 实例化时使用哪些依赖项。

3.2 依赖注入写法与非依赖注入写法

依赖注入写法:不关心依赖是如何构造的。

非依赖注入写法:必须自己初始化依赖,比如说 Repository 需要知道如何初始化 DAOCache。由此带来的缺点是:

  • 深度耦合依赖的初始化过程。
  • 往往需要定义额外的 Config 类型来传递依赖所需的配置信息。
  • 一旦依赖增加新的配置,或者更改了初始化过程,都要跟着修改。
  • 缺乏扩展性。
  • 测试不友好。
  • 难以复用公共组件,例如 DB 或 Redis 之类的客户端。

四、wire 工具介绍与安装

4.1 wire 基本介绍

  • Wire 是一个的 Google 开源专为依赖注入(Dependency Injection)设计的代码生成工具,通过自动生成代码的方式在初始编译过程中完成依赖注入。它可以自动生成用于化各种依赖关系的代码,从而帮助我们更轻松地管理和注入依赖关系。

  • Wire 分成两部分,一个是在项目中使用的依赖, 一个是命令行工具。

4.2 安装

go install github.com/google/wire/cmd/wire@latest

五、Wire 的基本使用

5.1 前置代码准备

目录结构如下:

wire
├── db.go                          # 数据库相关代码
├── go.mod                         # Go模块依赖配置文件
├── go.sum                         # Go模块依赖校验文件
├── main.go                        # 程序入口文件
├── repository                     # 存放数据访问层代码的目录
│   ├── dao                        # 数据访问对象(DAO)目录
│   │   └── user.go                # 用户相关的DAO实现
│   └── user.go                    # 用户仓库实现
├── wire.go                        # Wire依赖注入配置文件

repository/dao/user.go文件:

// repository/dao/user.go
package dao

import "gorm.io/gorm"

type UserDAO struct {
	db *gorm.DB
}

func NewUserDAO(db *gorm.DB) *UserDAO {
	return &UserDAO{
		db: db,
	}
}

repository/user.go 文件:

// repository/user.go
package repository

import "wire/repository/dao"

type UserRepository struct {
	dao *dao.UserDAO
}

func NewUserRepository(dao *dao.UserDAO) *UserRepository {
	return &UserRepository{
		dao: dao,
	}
}

db.go 文件:

// db.go
package wire

import (
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

func InitDB() *gorm.DB {
	db, err := gorm.Open(mysql.Open("dsn"))
	if err != nil {
		panic(err)
	}
	return db
}

main.go 文件:

package wire

import (
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"wire/repository"
	"wire/repository/dao"
)

func main() {
	// 非依赖注入
	db, err := gorm.Open(mysql.Open("dsn"))
	if err != nil {
		panic(err)
	}
	ud := dao.NewUserDAO(db)
	repo := repository.NewUserRepository(ud)
	fmt.Println(repo)
}

5.2 使用 Wire 工具生成代码

现在我们已经有了基本的代码结构,接下来我们将使用 wire 工具来生成依赖注入的代码。

首先,确保你已经安装了 wire 工具。如果没有安装,可以使用以下命令安装:

go get github.com/google/wire/cmd/wire

接下来,我们需要创建一个 wire 的配置文件,通常命名为 wire.go。在这个文件中,我们将使用 wire 的语法来指定如何构建 UserRepository 实例。

wire.go 文件:

//go:build wireinject

// 让 wire 来注入这里的代码
package wire

import (
	"github.com/google/wire"
	"wire/repository"
	"wire/repository/dao"
)

func InitRepository() *repository.UserRepository {
	// 我只在这里声明我要用的各种东西,但是具体怎么构造,怎么编排顺序
	// 这个方法里面传入各个组件的初始化方法
	wire.Build(InitDB, repository.NewUserRepository, dao.NewUserDAO)
	return new(repository.UserRepository)
}

这段代码是使用 wire 工具进行依赖注入的配置文件。在这个文件中,我们定义了一个函数 InitRepository,这个函数的目的是为了生成一个 *repository.UserRepository 的实例。但是,这个函数本身并不包含具体的实现代码,而是依赖于 wire 工具来注入依赖。
让我们逐步解释这段代码:

  1. 构建约束指令:

    //go:build wireinject
    

    这行注释是一个构建约束,它告诉 go build 只有在满足条件 wireinject 的情况下才应该构建这个文件。wireinject 是一个特殊的标签,用于指示 wire 工具处理这个文件。

  2. 导入包:

    import (
        "github.com/google/wire"
        "wire/repository"
        "wire/repository/dao"
    )
    

    这部分导入了必要的包,包括 wire 工具库,以及项目中的 repositorydao 包,这些包包含了我们需要注入的依赖。

  3. InitRepository 函数:

    func InitRepository() *repository.UserRepository {
        // 我只在这里声明我要用的各种东西,但是具体怎么构造,怎么编排顺序
        // 这个方法里面传入各个组件的初始化方法
        wire.Build(InitDB, repository.NewUserRepository, dao.NewUserDAO)
        return new(repository.UserRepository)
    }
    

    这个函数是 wire 注入的目标。它声明了一个返回 *repository.UserRepository 的函数,但是函数体内部没有具体的实现代码。wire.Build 函数调用是关键, 主要是连接或绑定我们之前定义的所有初始化函数。当我们运行 wire 工具来生成代码时,它就会根据这些依赖关系来自动创建和注入所需的实例。,这些函数按照依赖关系被调用,以正确地构造和注入 UserRepository 实例所需的依赖。

    • InitDB 是初始化数据库连接的函数。
    • repository.NewUserRepository 是创建 UserRepository 实例的函数。
    • dao.NewUserDAO 是创建 UserDAO 实例的函数。
      wire 工具会自动生成这些函数调用的代码,并确保依赖关系得到满足。
  4. 返回语句:

    return new(repository.UserRepository)
    

    这个返回语句是必须的,尽管它实际上并不会被执行。wire 工具会生成一个替换这个函数体的代码,其中包括所有必要的依赖注入逻辑。
    在编写完 wire.go 文件后,你需要运行 wire 命令来生成实际的依赖注入代码。生成的代码将被放在一个名为 wire_gen.go 的文件中,这个文件应该被提交到你的版本控制系统中。

现在,我们可以运行 wire 命令来生成依赖注入的代码:

wire

这个命令会扫描 wire.go 文件,并生成一个新的 Go 文件 wire_gen.go,其中包含了 InitializeUserRepository 函数的实现,这个函数会创建并返回一个 UserRepository 实例,其依赖项已经自动注入。

生成 wire_gen.go 文件,内容如下所示:

// Code generated by Wire. DO NOT EDIT.

//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinject

package wire

import (
	"wire/repository"
	"wire/repository/dao"
)

// Injectors from wire.go:

func InitRepository() *repository.UserRepository {
	db := InitDB()
	userDAO := dao.NewUserDAO(db)
	userRepository := repository.NewUserRepository(userDAO)
	return userRepository
}

最后,我们需要修改 main.go 文件,使用 wire 生成的代码来获取 UserRepository 实例:

package wire

func main() {
	InitRepository()
}

现在,当我们运行 main.go 时,它将使用 wire 工具生成的代码来初始化 UserRepository,包括其依赖的 UserDAO 和数据库连接。这样,我们就实现了依赖注入,并且代码更加简洁、易于维护。

六、Wire 核心技术

5.1 抽象语法树分析

wire 工具的工作原理是基于对Go代码的抽象语法树(Abstract Syntax Tree,简称AST)的分析。AST是源代码的抽象语法结构的树状表示,它以树的形式表现编程语言的语法结构。wire 工具通过分析AST来理解代码中的依赖关系。
在Go中,go/ast 包提供了解析Go源文件并构建AST的功能。wire 工具利用这个包来遍历和分析项目的Go代码,识别出所有的依赖项,并构建出依赖关系图。这个依赖关系图随后被用来生成注入依赖的代码。

5.2 模板编程

wire 工具生成代码的过程也涉及到模板编程。模板编程是一种编程范式,它允许开发者定义一个模板,然后使用具体的数据来填充这个模板,生成最终的代码或文本。
wire中,虽然不直接使用Go语言的模板引擎(如text/templatehtml/template),但它的工作原理与模板编程类似。wire定义了一套自己的语法来描述依赖关系,然后根据这些描述生成具体的Go代码。
wire的语法主要包括以下几个部分:

  • wire.NewSet:定义一组相关的依赖,通常包括一个或多个构造函数。
  • wire.Build:指定生成代码时应该使用哪些依赖集合。
  • bind 函数:用于绑定接口和实现,告诉wire如何创建接口的实例。
    wire工具通过这些语法来构建一个依赖图,然后根据这个图生成一个函数,该函数负责创建并返回所有必要的组件实例,同时处理它们之间的依赖关系。
    通过结合抽象语法树分析和模板编程,wire 工具能够提供一种声明式的依赖注入方法,让开发者能够专注于定义依赖关系,而不是手动编写依赖注入的代码。这不仅减少了重复劳动,还提高了代码的可维护性和降低了出错的可能性。

七、Wire 的核心概念

7.1 两个核心概念

wire 中,有两个核心概念:提供者(providers)和注入器(injectors)。

7.2 Wire 提供者(providers)

提供者 是一个普通有返回值的 Go 函数,它负责创建一个对象或者提供依赖。在 wire 的上下文中,提供者可以是任何返回一个或多个值的函数。这些返回值将成为注入器函数的参数。提供者函数通常负责初始化组件,比如数据库连接、服务实例等。并且提供者的返回值不仅限于一个,如果有需要的话,可以额外添加一个 error 的返回值。
例如,一个提供者函数可能会创建并返回一个数据库连接:

func NewDBConnection(dsn string) (*gorm.DB, error) {
    db, err := gorm.Open(mysql.Open(dsn))
    if err != nil {
        return nil, err
    }
    return db, nil
}

提供者函数可以分组为提供者函数集(provider set)。使用wire.NewSet 函数可以将多个提供者函数添加到一个集合中。举个例子,例如将 user 相关的 handlerservice 进行组合:

package web

var UserSet = wire.NewSet(NewUserHandler, service.NewUserService)

使用 wire.NewSet 函数将提供者进行分组,该函数返回一个 ProviderSet 结构体。不仅如此,wire.NewSet 还能对多个 ProviderSet 进行分组 wire.NewSet(UserSet, XxxSet)

package demo

import (
    // ...
    "example.com/some/other/pkg"
)

// ...

var MegaSet = wire.NewSet(UserSet, pkg.OtherSet)

7.3 Wire 注入器(injectors)

注入器(injectors)的作用是将所有的提供者(providers)连接起来,要声明一个注入器函数只需要在函数体中调用wire.Build()。这个函数的返回值也无关紧要,只要它们的类型正确即可。这些值在生成的代码中将被忽略。回顾一下我们之前的代码:

//go:build wireinject

// 让 wire 来注入这里的代码
package wire

import (
	"github.com/google/wire"
	"wire/repository"
	"wire/repository/dao"
)

func InitRepository() *repository.UserRepository {
	// 我只在这里声明我要用的各种东西,但是具体怎么构造,怎么编排顺序
	// 这个方法里面传入各个组件的初始化方法
	wire.Build(InitDB, repository.NewUserRepository, dao.NewUserDAO)
	return new(repository.UserRepository)
}

在这个例子中,InitRepository 是一个注入器,它依赖 InitDBrepository.NewUserRepository 这两个提供者。

与提供者一样,注入器也可以输入参数(然后将其发送给提供者),并且可以返回错误。wire.Build的参数和wire.NewSet一样:都是提供者集合。这些就在该注入器的代码生成期间使用的提供者集。

八、Wire 的高级用法

8.1 绑定接口

依赖项注入通常用于绑定接口的具体实现。wire通过类型标识将输入与输出匹配,因此倾向于创建一个返回接口类型的提供者。然而,这也不是习惯写法,因为Go的最佳实践是返回具体类型。你可以在提供者集中声明接口绑定.

我们对之前的代码进行改造:

首先,我们在UserRepository接口中定义一些方法。例如,我们可以定义一个GetUser方法,该方法接收一个用户ID,并返回相应的用户。 在repository/user.go文件中:

package repository

import (
    "wire/repository/dao"
    "gorm.io/gorm"
)

type UserRepository interface {
    GetUser(id uint) (*User, error)
}

type UserRepositoryImpl struct {
    dao *dao.UserDAO
}

func (r *UserRepositoryImpl) GetUser(id uint) (*User, error) {
    return r.dao.GetUser(id)
}

func NewUserRepository(dao *dao.UserDAO) UserRepository {
    return &UserRepositoryImpl{
        dao: dao,
    }
}

然后,我们在UserDAO中实现这个GetUser方法。在repository/dao/user.go文件中:

package dao

import (
    "gorm.io/gorm"
)

type User struct {
    ID uint
    // other fields...
}

type UserDAO struct {
    db *gorm.DB
}

func (dao *UserDAO) GetUser(id uint) (*User, error) {
    var user User
    result := dao.db.First(&user, id)
    if result.Error != nil {
        return nil, result.Error
    }
    return &user, nil
}

func NewUserDAO(db *gorm.DB) *UserDAO {
    return &UserDAO{
        db: db,
    }
}

最后,我们需要更新wire.go文件中的InitRepository函数,以返回UserRepository接口,而不是具体的实现。 在wire.go文件中:

//go:build wireinject

package wire

import (
    "github.com/google/wire"
    "wire/repository"
    "wire/repository/dao"
)

func InitRepository() repository.UserRepository {
    wire.Build(InitDB, repository.NewUserRepository, dao.NewUserDAO)
    return &repository.UserRepositoryImpl{}
}

使用 wire.Bind 来建立接口类型和具体的实现类型之间的绑定关系,这样 Wire 工具就可以根据这个绑定关系进行类型匹配并生成代码。

wire.Bind 函数的第一个参数是指向所需接口类型值的指针,第二个实参是指向实现该接口的类型值的指针。

8.2 结构体提供者(Struct Providers)

Wire 库有一个函数是 wire.Struct,它能根据现有的类型进行构造结构体,我们来看看下面的例子:

package main

import "github.com/google/wire"

type Name string

func NewName() Name {
	return "小米SU7"
}

type PublicAccount string

func NewPublicAccount() PublicAccount {
	return "新一代车神"
}

type User struct {
	MyName          Name
	MyPublicAccount PublicAccount
}

func InitializeUser() *User {
	wire.Build(
		NewName,
		NewPublicAccount,
		wire.Struct(new(User), "MyName", "MyPublicAccount"),
	)
	return &User{}
}

上述代码中,首先定义了自定义类型 NamePublicAccount 以及结构体类型 User,并分别提供了 NamePublicAccount 的初始化函数(providers)。然后定义一个注入器(injectorsInitializeUser,用于构造连接提供者并构造 *User 实例。

使用 wire.Struct 函数需要传递两个参数,第一个参数是结构体类型的指针值,另一个参数是一个可变参数,表示需要注入的结构体字段的名称集。

根据上述代码,使用 Wire 工具生成的代码如下所示:

func InitializeUser() *User {
    name := NewName()
    publicAccount := NewPublicAccount()
    user := &User{
       MyName:          name,
       MyPublicAccount: publicAccount,
    }
    return user
}

如果我们不想返回指针类型,只需要修改 InitializeUser 函数的返回值为非指针即可。

8.3 绑定值

有时,将基本值(通常为nil)绑定到类型是有用的。你可以向提供程序集添加一个值表达式,而不是让注入器依赖于一次性函数提供者(providers)。

func InjectUser() User {
    wire.Build(wire.Value(User{MyName: "小米SU7"}))
    return User{}
}

在上述代码中,使用 wire.Value 函数通过表达式直接指定 MyName 的值,生成的代码如下所示:

func InjectUser() User {
    user := _wireUserValue
    return user
}

var (
    _wireUserValue = User{MyName: "小米SU7"}
)

需要注意的是,值表达式将被复制到生成的代码文件中。

对于接口类型,可以使用 InterfaceValue

func InjectPostService() service.IPostService {
    wire.Build(wire.InterfaceValue(new(service.IPostService), &service.PostService{}))
    return nil
}

8.4 使用结构体字段作为提供者(providers)

有些时候,你可以使用结构体的某个字段作为提供者,从而生成一个类似 GetXXX 的函数。

func GetUserName() Name {
    wire.Build(
       NewUser,
       wire.FieldsOf(new(User), "MyName"),
    )
    return ""
}

你可以使用 wire.FieldsOf 函数添加任意字段,生成的代码如下所示:

func GetUserName() Name {
    user := NewUser()
    name := user.MyName
    return name
}

func NewUser() User {
    return User{MyName: Name("小米SU7"), MyPublicAccount: PublicAccount("新一代车神!")}
}

8.5 清理函数

如果一个提供者创建了一个需要清理的值(例如关闭一个文件),那么它可以返回一个闭包来清理资源。注入器会用它来给调用者返回一个聚合的清理函数,或者在注入器实现中稍后调用的提供商返回错误时清理资源。

func provideFile(log Logger, path Path) (*os.File, func(), error) {
    f, err := os.Open(string(path))
    if err != nil {
        return nil, nil, err
    }
    cleanup := func() {
        if err := f.Close(); err != nil {
            log.Log(err)
        }
    }
    return f, cleanup, nil
}

8.6 备用注入器语法

如果你不喜欢在注入器函数声明的末尾编写类似return Foo{}, nil的语句,那么你可以简单粗暴地使用panic

func InitializeGin() *gin.Engine {
    panic(wire.Build(/* ... */))
}

九、参考文档

  • 掘金依赖注入工具-wire
  • 李文周的博客-依赖注入工具-wire

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

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

相关文章

idea开发 java web 疫情信息查询系统bootstrap框架web结构java编程计算机网页接口查询

一、源码特点 java 疫情信息查询系统是一套完善的完整信息系统,结合java web开发和bootstrap UI框架完成本系统 ,对理解JSP java编程开发语言有帮助,系统具有完整的源代码和数据库,系统主要采用B/S模式开发。 前段主要技术 css j…

复习知识点整理

零碎语法 1.导入某个文件夹的index文件,index可以省略(这里导入的是router和store文件下的index.js文件) 2.路由懒加载 this 1.在vue文件中使用router\store对象时 this:普通函数的this指向vue实例对象(在没有明确指向的时候…

ubuntu系统设置java环境变量 jdk离线安装

目录 目的一、下载jdk二、安装三、配置jdk环境变量1、编辑文件~/.bashrc2、刷新系统环境变量3、验证 目的 在ubuntu环境下配置java环境变量,除jdk压缩包下载外,其余安装及配置过程无需联网,可离线完成。 一、下载jdk 一些比较靠谱的免登录…

Davinci报警40359

问题现象 前几天做实验的时候遇到个问题被卡着,报警40359。 问题描述 报警信息里面描述,我的一个IDT里面的基础类型有问题。 问题分析 根据提示排查 首先检查了出问题的IDT里面的基础类型,引用的是基础类型里面的布尔类型,非…

【Java】maven的下载和安装配置

Maven下载: 下载地址:http://maven.apache.org/download.cgi Maven 3.3 require JDK 1.7 or above : 因为Maven是一个纯粹的java实现的工具 下载完成后,得到一个压缩包,解压,可以看到maven的组成目录 Mav…

深入浅出 -- 系统架构之垂直架构

当业务复杂度增加、访问量逐渐增大出现高并发时,单体架构无法满足需求,可以根据业务功能对系统进行拆分,以提高访问效率。 垂直架构介绍 1.垂直架构一般是因为单体架构太过于庞大而进行的拆分,拆分后各个系统应满足独立运行互相不…

海外媒体宣发套餐推广8个要点解析为标题-华媒舍

在当前全球化的时代背景下,海外市场的开拓对于企业的发展至关重要。而海外媒体宣传是一种有效的推广方式,可以帮助企业在全球范围内打开市场。本文将对8个海外媒体宣发套餐的推广要点进行解析,帮助企业了解如何在海外市场进行宣传推广。 1. 媒…

uniapp极光推送、java服务端集成

一、准备工作 1、进入【服务中心】-【开发者平台】 2、【创建应用】,填写应用名称和图标(填写项目名称,项目logo就行,也可填写其他的) 3、选择【消息推送】服务,点击下一步 ​ ​ Demo测试 参照文档&…

4.网络编程-websocket(golang)

目录 什么是websocket golang中使用websocket Server端 Client端 什么是websocket WebSocket是一种在互联网上提供全双工通信的协议,即允许服务器和客户端之间进行双向实时通信的网络技术。它是作为HTML5的一部分标准化的,旨在解决传统HTTP协议在实…

C++ 之 【类与对象】从入门到精通一条龙服务 入门篇

不要觉的自己很没用,其实你还可以给家人带来温暖,比如爸妈看到你就来火 目录: 一、面向过程和面向对象初步认识 二、类的引入 三、类的定义 四、类的访问限定符及封装 1.访问限定符 2.封装 五、类的作用域 六、类的实例化 七、类的…

违法解除劳动合同——典型案例

目录 什么是违法解除劳动合同? 用人单位负举证责任 典型案例 案例:指导案例180号——违法解除劳动合同 案例:浙江省杭州市中级⼈⺠法院——违纪案例 案例:浙江省杭州市中级⼈⺠法院——违纪案例(不服从工作安排) 案例&#…

算法-数论-蓝桥杯

算法-数论 1、最大公约数 def gcd(a,b):if b 0:return areturn gcd(b, a%b) # a和b的最大公约数等于b与a mod b 的最大公约数def gcd(a,b):while b ! 0:cur aa bb cur%bpassreturn a欧几里得算法 a可以表示成a kb r(a,b,k&#xff0c…

13 Python进阶:pip及其他常用模块

pip 是 Python 包管理工具,它提供了对 Python 包的查找、下载、安装、卸载的功能。 包地址: https://pypi.org/ 最新的 Python 版本已经预装了 pip。 pip 各种命令 查看是否已经安装 pip 可以使用以下命令: pip --version下载安装包使用…

Mysql底层原理十一:Mvcc

为什么要mvcc? 提高并发度,如果读和写都是通过加锁的方式,并发肯定上不来,通过mvcc来实现写通过加锁,读通过mvcc readView机制 3.9.1 Undo版本链 再重复一遍,页面中的记录存放在用户表空间的数据页中&a…

并发编程之Java中Selector

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你的码吧。 Selector提供选择执…

【Web】CTFSHOW-2023CISCN国赛初赛刷题记录(全)

目录 Unzip BackendService go_session deserbug 主打一个精简 Unzip 进来先是一个文件上传界面 右键查看源码,actionupload.php 直接访问/upload.php,看到后端的源码 就是上传一个压缩包,对其进行解包处理 因为其是在/tmp下执行…

MySQL redo log和binlog的区别

redo log和binlog的区别 Redo Log(重做日志): 作用: Redo Log是InnoDB存储引擎特有的一种日志,用于确保数据库的事务持久性和恢复能力。 特点: 存储在InnoDB的数据文件中,而不是作为单独的日志…

CICD流水线 发布应用到docker镜像仓库

准备工作 1.先注册免费的镜像仓库 复制链接: https://cr.console.aliyun.com/cn-beijing/instances 实施 1. 新建流水线,选择模板 2.添加流水线源,及是你的代码仓库, 选择对应分支. 3.代码检查以及单元测试,这个步骤可以不用动它. 4. …

【C++】继承总结

一、前言 我们众所周知的C三大特性分别为:封装、继承、多态。 封装就是将接口实现统一化,隐藏那些不同的地方,在上层函数调用体现的方式一样,如各种容器的迭代器iterator,尽管底层实现的方式不同,但是在使用…

Java学习笔记23(面向对象三大特征)

1.5 多态 ​ *多态(polymorphic) ​ *方法或对象具有多种形态,是面向对象的第三大特征,多态是建立在封装和继承基础之上的 1.多态的具体体现 1.方法的多态 (重写和重载体现了多态) 2.对象的多态 ​ 1.一个对象的编译类型和…