go go go 出发咯 - go web开发入门系列(四) 数据库ORM框架集成与解读

go go go 出发咯 - go web开发入门系列(四) 数据库ORM框架集成与解读


往期回顾

  • go go go 出发咯 - go web开发入门系列(一) helloworld
  • go go go 出发咯 - go web开发入门系列(二) Gin 框架实战指南
  • go go go 出发咯 - go web开发入门系列(三) 项目基础框架搭建与解读

前言

在上一篇文章中,我们从零开始,搭建了一个生产级的 Go Web 应用框架。我们深入探讨了分层架构、依赖注入和面向接口编程,并最终构建了一个结构清晰、职责分明的“手动挡”应用——我们拥有对每一行 SQL 的完全控制权。

这种控制力在需要极致性能优化的场景下非常宝贵。但对于大多数标准的增删改查(CRUD)操作来说,手动编写和映射每一条 SQL 显得有些繁琐。这正是 ORM(对象关系映射)框架大显身手的舞台。

本文将作为上一篇的进阶,向您展示如何将 Go 生态中最流行的 ORM 框架 GORM,无缝地集成到我们现有的分层架构中。我们的目标是:在不改动任何 Service 和 Handler 层代码的前提下,用 GORM 完全替换掉手写 SQL 的 Repository 层,体验开发效率的飞跃。

架构回顾:解耦是替换的基石

让我们再次回顾一下我们的分层架构,正是这个清晰的结构,使得替换数据访问层成为可能。

/awesomeProject
| ├── cmd/ # 入口文件 
│ 	└── server/ 
│ 		└── main.go # 主程序入口 
├── configs/ # 配置文件 
│ 	└── config.dev.yaml 
├── internal/ # 内部模块 
│ ├── config/ # 配置加载 
│ ├── database/ # 数据库连接 
│ ├── models/ # 数据模型 
│ ├── repository/ # 数据访问层 
│ └── service/ # 业务逻辑层 
├── transport/ # 传输层 
...

GORM (全功能 ORM 框架)

GORM 介绍

GORM 是 Go 语言中最流行的全功能 ORM (Object-Relational Mapping) 框架。它的设计哲学最接近您熟悉的 HibernateMyBatis-Plus,旨在通过“约定优于配置”和链式调用,将开发者从手写 SQL 中解放出来。

核心理念: 将数据库操作完全对象化。你操作的是 Go 的结构体对象,GORM 负责在背后生成并执行对应的 SQL 语句。

特点:

链式 API: 提供非常流畅的链式调用方法 (db.Where(…).First(…))。

自动化: 自动处理创建、查询、更新、删除 (CRUD) 操作。

高级功能: 支持自动迁移(根据结构体创建/修改表)、钩子(在创建/更新前后执行特定函数)、预加载(Eager Loading)、事务等。

优点:

  • 开发效率极高,尤其适合快速构建原型和标准的 CRUD 应用。

  • 代码量显著减少,可读性强(对于熟悉 ORM 的人而言)。

缺点:“魔法”太多,可能会隐藏底层 SQL 的性能问题。

  • 对于复杂的查询,其链式 API 可能变得复杂,或者不得不退回手写 SQL。

  • 学习曲线相对较陡,需要理解其内部的约定和工作方式。

GORM 代码集成示例

本节我们将继续在次框架上,进行实现商品(product)相关的CRUD操作,并给与外部调用,对于商品(product)整个链路过程将采用ORM的方式,便于和之前实现的用户(User)相对比学习。

第1步:安装 GORM 及驱动

GORM 的工作需要两个核心组件:GORM 核心库和对应数据库的驱动。

# 安装 GORM 核心库
go get -u gorm.io/gorm# 安装 GORM 的 MySQL 驱动适配器
go get -u gorm.io/driver/mysql

还记得在上一节中我们连接 mysql 数据库时引入的依赖 go get -u github.com/go-sql-driver/mysql 吗?

Q:" go get -u github.com/go-sql-driver/mysql ; go get -u gorm.io/gorm ; go get -u gorm.io/driver/mysql " 这三个依赖不冲突吗?

A:

gorm.io/gorm (ORM 框架本身)

  • 这是 GORM 的核心库,提供了所有 .Create(), .First(), .Where() 等链式调用方法。
  • 它是一个高层抽象,负责将对象操作转换为 SQL 思想。但它自己并不知道如何与具体的数据库(如 MySQL 或 PostgreSQL)对话。

gorm.io/driver/mysql (GORM 的 MySQL 适配器)

  • 这个库是连接 GORM 核心框架和底层数据库驱动的“桥梁”或“适配器”。
  • 它告诉 GORM:“当你需要操作 MySQL 时,应该使用这种方式来配置和传递指令。”

github.com/go-sql-driver/mysql (底层的数据库驱动)

  • 这是真正负责与 MySQL 服务器进行网络通信、执行 SQL 语句的“工人”。
  • gorm.io/driver/mysql 这个“适配器”在内部会依赖并调用这个底层的驱动来完成实际工作。

gorm.io/driver/mysql 在底层依赖了 go-sql-driver/mysql,Go 的模块工具会自动处理这个依赖关系。

第2步:创建领域模型 (Model)

我们在 internal/models 下创建产品(product)结构体,对比 SpringBoot 作为数据库实体映射

GORM 可以通过嵌入 gorm.Model 来为我们的结构体自动添加 ID, CreatedAt, UpdatedAt, DeletedAt 等常用字段。

// internal/models/product.go
package modelsimport "gorm.io/gorm"type product struct {gorm.Model         // 嵌入gorm.Model,自动获得ID和时间戳字段Name       string  `gorm:"size:255;not null"` // 使用 GORM 标签定义列属性Price      float64 `gorm:"type:decimal(10,2)"`Stock      int     `gorm:"default:0"`
}//上述结构体等价于//type product struct {
//  ID        uint           `gorm:"primaryKey"`
//  CreatedAt time.Time
//  UpdatedAt time.Time
//  DeletedAt gorm.DeletedAt `gorm:"index"`
//  Name       string  `gorm:"size:255;not null"` // 使用 GORM 标签定义列属性
//  Price      float64 `gorm:"type:decimal(10,2)"`
//  Stock      int     `gorm:"default:0"`
//}
//
第3步:创建 GORM 数据库连接

internal/database/ 下创建 gorm.go 来初始化 GORM 的数据库连接。

//internal/database/gorm.go
package databaseimport ("awesomeProject/internal/config""gorm.io/driver/mysql""gorm.io/gorm"
)// NewGormConnection 负责根据配置创建GORM数据库连接池
func NewGormConnection(dbConfig config.DatabaseConfig) (*gorm.DB, error) {// dsn 来自于我们的配置文件dsn := dbConfig.DSN// 使用GORM的MySQL驱动来打开数据库连接db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})if err != nil {return nil, err}// 获取底层的 *sql.DB 对象来设置连接池参数sqlDB, err := db.DB()if err != nil {return nil, err}// 设置从配置中读取的连接池参数sqlDB.SetMaxIdleConns(dbConfig.MaxIdleConns)sqlDB.SetMaxOpenConns(dbConfig.MaxOpenConns)// 可以选择在这里 Ping 数据库以验证连接if err = sqlDB.Ping(); err != nil {return nil, err}return db, nil
}
第4步:实现关于"产品"的 GORM Repository

internal/repository/ 下创建 ProductRepository.go 使用GORM 来操作数据库,生成对于product的CURD 方法

//internal/repository/ProductRepository.go
package repositoryimport ("awesomeProject/internal/models""context""gorm.io/gorm"
)// ProductRepository 定义了产品数据的所有操作,便于解耦
type ProductRepository interface {Create(ctx context.Context, product *models.Product) errorFindByID(ctx context.Context, id int64) (*models.Product, error)FindAll(ctx context.Context) ([]*models.Product, error)Update(ctx context.Context, product *models.Product) errorDelete(ctx context.Context, id int64) error
}// 构造方法
type gormMySqlProductRepository struct {db *gorm.DB // 应用的是grom 框架 这里持有的是 *gorm.DB 而不是 *sql.DB
}func (g gormMySqlProductRepository) Create(ctx context.Context, product *models.Product) error {result := g.db.WithContext(ctx).Create(product)return result.Error
}func (g gormMySqlProductRepository) FindByID(ctx context.Context, id int64) (*models.Product, error) {var product models.Productresult := g.db.WithContext(ctx).First(&product, id)if result.Error != nil {if result.Error == gorm.ErrRecordNotFound {return nil, nil}return nil, result.Error}return &product, nil}func (g gormMySqlProductRepository) FindAll(ctx context.Context) ([]*models.Product, error) {var products []*models.Productresult := g.db.WithContext(ctx).Find(&products)if result.Error != nil {if result.Error == gorm.ErrRecordNotFound {return nil, nil}return nil, result.Error}return products, nil
}func (g gormMySqlProductRepository) Update(ctx context.Context, product *models.Product) error {result := g.db.WithContext(ctx).Save(product)if result.Error != nil {return result.Error}return nil
}func (g gormMySqlProductRepository) Delete(ctx context.Context, id int64) error {result := g.db.WithContext(ctx).Delete(&models.Product{}, id)return result.Error
}// NewProductRepository 创建一个新的 ProductRepository 实例
func NewProductRepository(db *gorm.DB) ProductRepository {return &gormMySqlProductRepository{db: db}
}

Q: "gorm 官方文档中显示可以使用db.create直接操作数据库,比如: 新增数据直接使用db.create(bean),但是上述代码中使用的是 g.db.WithContext(ctx).create 这是为什么 "?

A:虽然直接使用 r.db.Create(product) 在功能上可以成功插入数据,但使用 r.db.WithContext(ctx).Create(product) 是一种更专业、更具弹性的最佳实践

详细解释一下 WithContext(ctx) 带来的三大好处:

1. 请求取消传播 (Cancellation Propagation)

  • 场景: 一个用户向您的服务器发送了创建产品的请求,但这个请求需要执行一个耗时很长的数据库操作。在操作完成前,用户不耐烦地关闭了浏览器,或者网络中断了。
  • 不使用 WithContext 的情况: 您的服务器对此一无所知。即使请求的另一端已经没人等待了,数据库操作依然会继续执行,直到完成。这白白浪费了宝贵的数据库连接和服务器资源。
  • 使用 WithContext 的情况: Gin 会为每个 HTTP 请求创建一个 context (ctx)。当用户断开连接时,Gin 会“取消”这个 ctxWithContext(ctx) 会将这个“取消”信号传递给 GORM,GORM 再传递给底层的数据库驱动。驱动程序收到信号后,可以提前终止那个正在执行的、已经没有意义的数据库查询,从而立即释放资源。

2. 超时控制 (Timeout Control)

  • 场景: 您可以为整个请求或某个特定的操作设置一个超时时间。比如,您规定任何数据库操作都不能超过3秒。
  • 不使用 WithContext 的情况: 如果某个数据库查询因为锁或者性能问题卡住了,它可能会永远地挂起,永久性地占用一个数据库连接,直到数据库自己超时。
  • 使用 WithContext 的情况: 您可以在 Service 层或 Handler 层创建一个带超时的 context (e.g., context.WithTimeout(ctx, 3*time.Second))。如果数据库操作在3秒内没有完成,ctx 会自动被取消。WithContext 感知到这个取消信号后,会立即终止数据库操作,并返回一个超时错误。这可以有效地防止慢查询拖垮整个系统。

3. 传递元数据 (Passing Metadata)

  • 场景: 在复杂的微服务架构中,您需要追踪一个请求经过了哪些服务。通常会有一个全局唯一的“追踪ID (Trace ID)”。
  • context 的作用: context 是在函数调用链中安全地传递这类请求范围内的元数据(如 Trace ID)的标准方式,而无需修改每个函数的参数列表。GORM 和很多其他库都能与 OpenTelemetry 等链路追踪系统集成,从 ctx 中提取这些信息用于日志和监控。
第5步:实现关于"产品"的 productService

之前在实现 userservice 时,对于 userservice 我们没有做到抽象成接口的形式,直接将userservice 做结构体进行声明,在此我会将productservice进行抽象,抽象成一个接口的形式。

package serviceimport ("awesomeProject/internal/models"     "awesomeProject/internal/repository" "context""errors" // 导入errors包,用于创建自定义错误
)// ProductService 定义了产品相关的业务逻辑接口
type ProductService interface {CreateProduct(ctx context.Context, name string, price float64, stock int) (*models.Product, error)GetProduct(ctx context.Context, id int64) (*models.Product, error)GetAllProducts(ctx context.Context) ([]*models.Product, error)UpdateProduct(ctx context.Context, id int64, name string, price float64, stock int) (*models.Product, error)DeleteProduct(ctx context.Context, id int64) error
}// productService 是 ProductService 的具体实现
type productService struct {productRepo repository.ProductRepository // 它依赖于 ProductRepository 接口,而不是具体实现
}// NewProductService 是 ProductService 的构造函数
func NewProductService(repo repository.ProductRepository) ProductService {return &productService{productRepo: repo}
}// CreateProduct 处理创建新产品的业务逻辑
func (s *productService) CreateProduct(ctx context.Context, name string, price float64, stock int) (*models.Product, error) {// 在这里可以添加业务逻辑,例如:// 1. 验证产品名称是否有效if name == "" {return nil, errors.New("product name cannot be empty")}// 2. 验证价格是否合法if price <= 0 {return nil, errors.New("product price must be positive")}// 3. 产品库存是否合法if stock < 0 {return nil, errors.New("product stock cannot be negative")}// 创建一个新的产品模型实例product := &models.Product{Name:  name,Price: price, Stock: stock,}// 调用仓库层来持久化数据err := s.productRepo.Create(ctx, product)if err != nil {return nil, err}return product, nil
}// GetProduct 处理获取单个产品的业务逻辑
func (s *productService) GetProduct(ctx context.Context, id int64) (*models.Product, error) {// 直接调用仓库层。return s.productRepo.FindByID(ctx, id)
}// GetAllProducts 处理获取所有产品的业务逻辑
func (s *productService) GetAllProducts(ctx context.Context) ([]*models.Product, error) {return s.productRepo.FindAll(ctx)
}// UpdateProduct 处理更新产品的业务逻辑
func (s *productService) UpdateProduct(ctx context.Context, id int64, name string, price float64, stock int) (*models.Product, error) {// 1. 首先,获取要更新的产品product, err := s.productRepo.FindByID(ctx, id)if err != nil {return nil, err // 如果在查找过程中发生数据库错误}if product == nil {return nil, errors.New("product not found") // 如果产品不存在}// 2. 更新产品的字段product.Name = nameproduct.Price = priceproduct.Stock = stock// 3. 在这里可以添加更复杂的验证逻辑...// 4. 调用仓库层的更新方法err = s.productRepo.Update(ctx, product)if err != nil {return nil, err}return product, nil
}// DeleteProduct 处理删除产品的业务逻辑
func (s *productService) DeleteProduct(ctx context.Context, id int64) error {// 在删除前,可以添加权限检查等业务逻辑// 例如:检查当前用户是否有权限删除该产品return s.productRepo.Delete(ctx, id)
}
第6步:实现全新的productHandler.go
package httpimport ("awesomeProject/internal/service" "github.com/gin-gonic/gin""net/http""strconv"
)// ProductHandler 负责处理产品相关的HTTP请求
type ProductHandler struct {productService service.ProductService // 它依赖于 ProductService 接口
}// NewProductHandler 是 ProductHandler 的构造函数
func NewProductHandler(svc service.ProductService) *ProductHandler {return &ProductHandler{productService: svc}
}// CreateProduct godoc
// @Summary      创建一个新产品
// @Description  根据传入的JSON数据创建一个新产品
// @Tags         Products
// @Accept       json
// @Produce      json
// @Param        product  body      CreateProductRequest  true  "创建产品请求"
// @Success      201      {object}  models.Product
// @Failure      400      {object}  gin.H
// @Failure      500      {object}  gin.H
// @Router       /products [post]
func (h *ProductHandler) CreateProduct(c *gin.Context) {// 定义一个临时的结构体来绑定请求的JSON bodyvar req struct {Name  string  `json:"name" binding:"required"`Price float64 `json:"price" binding:"gt=0"`Stock int     `json:"stock" binding:"gte=0"`}// 解析并验证JSON请求体if err := c.ShouldBindJSON(&req); err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request data: " + err.Error()})return}// 调用Service层来创建产品product, err := h.productService.CreateProduct(c.Request.Context(), req.Name, req.Price, req.Stock)if err != nil {// 根据Service层返回的错误类型,可以返回更具体的HTTP状态码c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})return}// 返回201 Created状态码和创建成功的产品信息c.JSON(http.StatusCreated, product)
}// GetProduct godoc
// @Summary      获取单个产品
// @Description  根据产品ID获取产品详情
// @Tags         Products
// @Produce      json
// @Param        id   path      int  true  "产品ID"
// @Success      200  {object}  models.Product
// @Failure      400  {object}  gin.H
// @Failure      404  {object}  gin.H
// @Failure      500  {object}  gin.H
// @Router       /products/{id} [get]
func (h *ProductHandler) GetProduct(c *gin.Context) {// 从URL路径中获取ID参数id, err := strconv.ParseInt(c.Param("id"), 10, 64)if err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid product ID"})return}// 调用Service层获取产品product, err := h.productService.GetProduct(c.Request.Context(), id)if err != nil {c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})return}// 如果Service层返回nil,说明产品不存在if product == nil {c.JSON(http.StatusNotFound, gin.H{"error": "Product not found"})return}c.JSON(http.StatusOK, product)
}// GetAllProducts godoc
// @Summary      获取所有产品列表
// @Description  获取数据库中所有产品的列表
// @Tags         Products
// @Produce      json
// @Success      200  {array}   models.Product
// @Failure      500  {object}  gin.H
// @Router       /products [get]
func (h *ProductHandler) GetAllProducts(c *gin.Context) {products, err := h.productService.GetAllProducts(c.Request.Context())if err != nil {c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})return}c.JSON(http.StatusOK, products)
}// UpdateProduct godoc
// @Summary      更新一个产品
// @Description  根据ID和传入的JSON数据更新一个已存在的产品
// @Tags         Products
// @Accept       json
// @Produce      json
// @Param        id       path      int                   true  "产品ID"
// @Param        product  body      UpdateProductRequest  true  "更新产品请求"
// @Success      200      {object}  models.Product
// @Failure      400      {object}  gin.H
// @Failure      404      {object}  gin.H
// @Failure      500      {object}  gin.H
// @Router       /products/{id} [put]
func (h *ProductHandler) UpdateProduct(c *gin.Context) {id, err := strconv.ParseInt(c.Param("id"), 10, 64)if err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid product ID"})return}var req struct {Name  string  `json:"name" binding:"required"`Price float64 `json:"price" binding:"gt=0"`Stock int     `json:"stock" binding:"gte=0"`}if err := c.ShouldBindJSON(&req); err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request data: " + err.Error()})return}product, err := h.productService.UpdateProduct(c.Request.Context(), id, req.Name, req.Price, req.Stock)if err != nil {// 这里可以根据service返回的错误类型判断是404还是500c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})return}c.JSON(http.StatusOK, product)
}// DeleteProduct godoc
// @Summary      删除一个产品
// @Description  根据ID删除一个产品
// @Tags         Products
// @Produce      json
// @Param        id   path      int  true  "产品ID"
// @Success      204  {object}  nil
// @Failure      500  {object}  gin.H
// @Router       /products/{id} [delete]
func (h *ProductHandler) DeleteProduct(c *gin.Context) {id, err := strconv.ParseInt(c.Param("id"), 10, 64)if err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid product ID"})return}err = h.productService.DeleteProduct(c.Request.Context(), id)if err != nil {c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})return}// 对于删除操作,成功后通常返回 204 No Contentc.Status(http.StatusNoContent)
}
第7步:实现全新的main.go

实现全新的main.go

package mainimport ("awesomeProject/internal/config""awesomeProject/internal/database""awesomeProject/internal/repository""awesomeProject/internal/service""awesomeProject/transport/http"_ "gorm.io/gorm""log""github.com/gin-gonic/gin"
)func main() {// 1. 加载配置cfg, err := config.Load("./configs/config.dev.yaml")if err != nil {log.Fatalf("FATAL: Failed to load config: %v", err)}// 2. 初始化GORM数据库连接// 我们将GORM的初始化逻辑也封装到了database包中,使main.go更整洁db, err := database.NewGormConnection(cfg.Database)if err != nil {log.Fatalf("FATAL: Failed to connect to database: %v", err)}log.Println("Database connection established successfully.")// 3. 依赖注入:将所有组件连接起来// 数据流向: Handler -> Service -> Repository -> Database// a. 创建 Repository 实例,它依赖 GORM 的数据库连接(db)productRepo := repository.NewProductRepository(db)// b. 创建 Service 实例,它依赖 Repository 层的接口(productRepo)productService := service.NewProductService(productRepo)// c. 创建 Handler 实例,它依赖 Service 层的接口(productService)productHandler := http.NewProductHandler(productService)// 4. 初始化 Gin 路由引擎router := gin.Default()// 5. 注册产品相关的路由// 创建一个API分组,方便管理版本,例如 /api/v1apiV1 := router.Group("/api/v1"){products := apiV1.Group("/products"){products.POST("", productHandler.CreateProduct)       // 创建产品products.GET("", productHandler.GetAllProducts)       // 获取所有产品products.GET("/:id", productHandler.GetProduct)       // 获取单个产品products.PUT("/:id", productHandler.UpdateProduct)    // 更新产品products.DELETE("/:id", productHandler.DeleteProduct) // 删除产品}}// 6. 启动服务器log.Println("Starting server on port :8080")if err := router.Run(":8080"); err != nil {log.Fatalf("FATAL: Failed to start server: %v", err)}
}
接口调用测试

添加商品成功

请添加图片描述

查询商品

请忽略中间的参数,懒得没删除而已

请添加图片描述

查询全部商品

请添加图片描述

更新编号为1的商品

更新前:

请添加图片描述

更新后:

请添加图片描述

删除编号为1的商品:

请添加图片描述

删除后查询

请添加图片描述

数据库建设

products.sql

-- auto-generated definition
create table products
(id         bigint unsigned auto_incrementprimary key,created_at datetime(3)      null,updated_at datetime(3)      null,deleted_at datetime(3)      null,name       varchar(255)     not null,price      decimal(10, 2)   null,stock      bigint default 0 null
);create index idx_products_deleted_aton products (deleted_at);

数据表说明:

products: GORM 默认会将结构体名称 Product 转换为蛇形复数形式作为表名。

gorm.Model: 嵌入的 gorm.Model 自动为添加了 id, created_at, updated_at, deleted_at 四个核心字段。deleted_at 用于实现 GORM 的软删除功能。

请添加图片描述

总结

通过将 GORM 集成到我们的分层架构中,我们实现了一个完美的平衡:

  • 获得了 ORM 带来的高开发效率:告别了繁琐的 SQL 编写和手动映射。
  • 保留了清晰的架构和解耦:各层职责分明,易于维护和测试。

有用的网站:

  • GORM 指南 | GORM 中文文档

代码仓库:

🌍代码框架链接


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

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

相关文章

CD47.【C++ Dev】list的模拟实现(2)

目录 1.const修饰的迭代器的实现 方法1:分成两个类 完整代码 方法2:STL库的写法 2.STL库的第三个模版参数T*的解释 ->->的简写语法 3.其他成员函数 insert erase push_back、push_front、pop_front、pop_back size clear 析构函数~list() 拷贝构造函数(★…

UI前端与数字孪生融合新领域拓展:智慧教育的虚拟实验室建设

hello宝子们...我们是艾斯视觉擅长ui设计、前端开发、数字孪生、大数据、三维建模、三维动画10年经验!希望我的分享能帮助到您!如需帮助可以评论关注私信我们一起探讨!致敬感谢感恩!一、引言&#xff1a;虚拟实验室 —— 打破教育边界的技术革命传统实验教学正面临 “设备昂贵、…

7. TCP 和 UDP 的区别

总结 TCP 面向连接&#xff0c;需要三次握手建立连接&#xff0c;UDP 无连接&#xff0c;不需要握手&#xff0c;直接发送数据。UDP 有较好的实时性&#xff0c;效率比 TCP 高。TCP 面向字节流&#xff0c;实际上是 TCP 把数据看成一连串无结构的字节流&#xff0c;UDP 是面向报…

iOS Widget 开发-7:TimelineProvider 机制全解析:构建未来时间线

在 WidgetKit 中&#xff0c;TimelineProvider 是小组件生命周期的核心机制之一。它控制着 数据获取时机、展示内容 与 刷新策略&#xff0c;是实现时间驱动内容更新的基础。 本文将介绍 TimelineProvider 的工作原理、设计模式、常见场景与高级用法&#xff0c;帮助大家构建智…

基于PHP/MySQL的企业培训考试系统源码,高并发、稳定运行,源码开源可二开

温馨提示&#xff1a;文末有资源获取方式这是一款专为企业设计的开源培训考试系统&#xff0c;采用PHPMySQL技术栈开发&#xff0c;具有高并发处理能力和稳定运行特性。系统源码完全开放&#xff0c;支持二次开发&#xff0c;可满足各类企业的培训考核需求。核心功能特点1. 高性…

时序数据库InfluxDB

一.定义 时序数据库 是一种专门用于高效存储和查询带有时间戳的数据的数据库。如果你的数据是随着时间变化而不断产生&#xff0c;并且你想知道过去某一时刻发生了什么&#xff0c;那么你应该用时序数据库。 这类数据通常具有以下特征&#xff1a; 数据点按时间顺序不断写入…

2025.07.09华为机考真题解析-第三题300分

📌 点击直达笔试专栏 👉《大厂笔试突围》 💻 春秋招笔试突围在线OJ 👉 笔试突围OJ 03. 博物馆安保摄像头配置 问题描述 A先生负责为一家新开的博物馆设计安保监控系统。博物馆有多个展厅需要监控,每个展厅都有不同的面积。现在有多种型号的监控摄像头可供选择,每…

存储过程封装:复杂业务逻辑的性能优化

存储过程作为数据库层面的重要功能&#xff0c;能够显著提升复杂业务逻辑的执行效率。以下是存储过程在性能优化中的核心优势、实现策略和实际应用场景。一、存储过程的核心优势‌网络传输压缩‌存储过程将多条SQL语句封装为单次调用&#xff0c;相比应用层多次请求可减少60%-8…

逗号分隔字段统计秘籍:一条SQL实现逗号分割字段的数量分析

一、问题场景与痛点 在数据库设计中&#xff0c;经常会遇到统计某一些数据的最大数量最小数量等&#xff0c;特别是**逗号分隔字段 **的统计会显得非常困难 下面以我生产上遇到的一个问题讲解&#xff1a; 有个需求是在o_work_order表中统计sn字段中哪个工单号的数量最多&#…

数据库性能优化指南:解决ORDER BY导致的查询性能问题( SQL Server )

数据库性能优化指南&#xff1a;解决ORDER BY导致的查询性能问题 问题描述 在300万行的INTERFACE_INTERACTION_LOG表中执行以下查询&#xff1a; SELECT TOP 1 * FROM INTERFACE_INTERACTION_LOG WHERE 1 1AND (SENDSTATUS 0 OR SENDSTATUS -1)AND SENDMETHOD POSTAND ERRO…

Centos 7下使用C++使用Rdkafka库实现生产者消费者

1. 了解 Kafka Apache Kafka 是一个分布式流处理平台&#xff0c;核心功能包括&#xff1a; 发布/订阅消息系统&#xff1a;解耦生产者和消费者 分布式存储&#xff1a;持久化、容错的消息存储 流处理&#xff1a;实时处理数据流 核心概念&#xff1a; 概念说明BrokerKaf…

UE5多人MOBA+GAS 13、添加死亡、复活逻辑以及布娃娃含物理资产的修改调整

文章目录使用GE为角色添加定时的Tag控制死亡时间1、添加死亡Tag2、创建死亡GE&#xff0c;并完成相关配置3、在AbilitySystemComponent中监听属性的变化&#xff0c;调用GE来添加Tag到角色上4、在角色中监听ASC传入的Tag以及Tag的层数&#xff0c;来响应不同的函数添加死亡、复…

Jiasou TideFlow重塑AI SEO全链路自动化新标杆

引言 在Google日均处理85亿次搜索请求的数字化浪潮中&#xff0c;传统SEO工作流面临三大致命瓶颈&#xff1a;人工拓词效率低下、跨部门协作成本高企、数据监控链路断裂。因此诸如Jiasou AI SEO这样专门为AI SEO而生的Agent就应运而生了。 背景 Jiasou AIGC不仅仅可以批量生成…

CentOs 7 MySql8.0.23之前的版本主从复制

准备俩台虚拟机并启动俩台虚拟机都开启mysql后查看二进制日志是否开启先登录mysqlmysql -u root -r输入sql命令show variables like %log_bin%;如果log_bin 的value为OFF则是没有开启&#xff0c;跟着下面步骤开启二进制日志退出mysqlexitvim /etc/my.cnf在最底下添加log_binmy…

Leetcode 3607. Power Grid Maintenance

Leetcode 3607. Power Grid Maintenance 1. 解题思路2. 代码实现 题目链接&#xff1a;3607. Power Grid Maintenance 1. 解题思路 这一题思路上首先是一个DSU的思路&#xff0c;将所有的连通网络计算出来&#xff0c;并对每一个网络的节点进行归类。然后我们需要对每一个网…

开源 python 应用 开发(三)python语法介绍

最近有个项目需要做视觉自动化处理的工具&#xff0c;最后选用的软件为python&#xff0c;刚好这个机会进行系统学习。短时间学习&#xff0c;需要快速开发&#xff0c;所以记录要点步骤&#xff0c;防止忘记。 链接&#xff1a; 开源 python 应用 开发&#xff08;一&#xf…

1-Kafka介绍及常见应用场景

Kafka 介绍 Apache Kafka 是一个开源的 分布式流处理平台&#xff0c;最初由 LinkedIn 开发&#xff0c;后捐赠给 Apache 软件基金会。它被设计用于高吞吐量、低延迟、可水平扩展地处理实时数据流。官网地址是&#xff1a;https://kafka.apache.org/ 以下是 Kafka 的核心介绍…

CH9121T电路及配置详解

目录1. CH9121T简介2. 原理图及接口2.1 参考电路2.2 CH9121T评估板2.3 差分端口2.4 网口灯显示2.5 晶振2.6 其他接口3. 使用手册及说明3.1 配置介绍3.2 默认参数3.3 串口波特率3.4 配置指令3.5 应用示例1. CH9121T简介 CH9121 是一款网络串口透传芯片&#xff0c;自带 10/100M…

科研数据可视化核心技术:基于 AI 与 R 语言的热图、火山图及网络图绘制实践指南

在学术研究竞争日趋激烈的背景下&#xff0c;高质量的数据可视化已成为科研成果呈现与学术传播的关键要素。据统计&#xff0c;超过 60% 的学术稿件拒稿原因与图表质量存在直接关联&#xff0c;而传统绘图工具在处理组学数据、复杂关联数据时&#xff0c;普遍存在效率低下、规范…

Windows体验macOS完整指南

一、虚拟机安装macOS专业方案1. 环境准备阶段硬件检测&#xff1a;进入BIOS&#xff08;开机时按Del/F2键&#xff09;确认开启VT-x/AMD-V虚拟化选项建议配置&#xff1a;i5十代以上CPU/16GB内存/256GB SSD软件准备&#xff1a;官网下载VMware Workstation 17 Pro获取Unlocker补…