gRPC学习笔记记录以及整合gin开发
gprc基础
前置环境准备
grpc下载
项目目录下执行
go get google.golang.org/grpc@latest
Protocol Buffers v3
https://github.com/protocolbuffers/protobuf/releases/download/v3.20.1/protoc-3.20.1-linux-x86_64.zip
go语言插件:
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28
rpc插件
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2
gRPC Hello World快速上手
基本rpc调用
服务端protoc编写
定义一个hello.proto文件
syntax = "proto3"; // 版本声明,使用Protocol Buffers v3版本option go_package = "hello_server/pb"; // 指定生成的Go代码在你项目中的导入路径package pb; // 包名// 定义服务
service Greeter {// SayHello 方法rpc SayHello (HelloRequest) returns (HelloResponse) {}
}// 请求消息
message HelloRequest {string name = 1;
}// 响应消息
message HelloResponse {string reply = 1;
}
服务端编写
package mainimport ("context""fmt""hello_server/pb""net""google.golang.org/grpc"
)// hello servertype server struct {pb.UnimplementedGreeterServer
}func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) {return &pb.HelloResponse{Reply: "Hello " + in.Name}, nil
}func main() {// 监听本地的8972端口lis, err := net.Listen("tcp", ":8972")if err != nil {fmt.Printf("failed to listen: %v", err)return}s := grpc.NewServer() // 创建gRPC服务器pb.RegisterGreeterServer(s, &server{}) // 在gRPC服务端注册服务// 启动服务err = s.Serve(lis)if err != nil {fmt.Printf("failed to serve: %v", err)return}
}
客户端protoc编写
syntax = "proto3"; // 版本声明,使用Protocol Buffers v3版本option go_package = "hello_client/pb"; // 指定生成的Go代码在你项目中的导入路径package pb; // 包名// 定义服务
service Greeter {// SayHello 方法rpc SayHello (HelloRequest) returns (HelloResponse) {}
}// 请求消息
message HelloRequest {string name = 1;
}// 响应消息
message HelloResponse {string reply = 1;
}
客户端编写
package mainimport ("context""flag""log""time""hello_client/pb""google.golang.org/grpc""google.golang.org/grpc/credentials/insecure"
)// hello_clientconst (defaultName = "world"
)var (addr = flag.String("addr", "127.0.0.1:8972", "the address to connect to")name = flag.String("name", defaultName, "Name to greet")
)func main() {flag.Parse()// 连接到server端,此处禁用安全传输conn, err := grpc.NewClient(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))if err != nil {log.Fatalf("did not connect: %v", err)}defer conn.Close()c := pb.NewGreeterClient(conn)// 执行RPC调用并打印收到的响应数据ctx, cancel := context.WithTimeout(context.Background(), time.Second)defer cancel()r, err := c.SayHello(ctx, &pb.HelloRequest{Name: *name})if err != nil {log.Fatalf("could not greet: %v", err)}log.Printf("Greeting: %s", r.GetReply())
}
分别在客户端和服务端执行如下程序生成代码
protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
pb/hello.proto
生成服务端目录结构
生成客户端目录结构
服务端编译执行
go build编译生成hello_server
执行
客户段编译执行
现在来看看为啥客户端会打印”Hello 哈哈哈“
首先看下客户端main函数
其中有一行关键代码
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: *name})
这里调用了SayHello函数而客户段的Sayhello函数又调用了服务端的SayHello函数
服务端SayHello函数
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) {return &pb.HelloResponse{Reply: "Hello " + in.Name}, nil
}
流式rpc调用
服务端流式调用
在原有基础上proto文件上增加如下函数
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);
重新生成代码
重新go build
服务端增加LotsOfReplies实现
// LotsOfReplies 返回使用多种语言打招呼
func (s *server) LotsOfReplies(in *pb.HelloRequest, stream pb.Greeter_LotsOfRepliesServer) error {words := []string{"你好","hello","こんにちは","안녕하세요",}for _, word := range words {data := &pb.HelloResponse{//循环拼接打招呼信息与客户端传过来的用户Reply: word + in.GetName(),}// 拼接打招呼信息使用流式的Send方法返回多个数据if err := stream.Send(data); err != nil {return err}}return nil
}
客户端增加LotsOfReplies实现
func runLotsOfReplies(c pb.GreeterClient) {// server端流式RPC// 延长超时时间避免中断ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)defer cancel()stream, err := c.LotsOfReplies(ctx, &pb.HelloRequest{Name: *name})if err != nil {log.Fatalf("c.LotsOfReplies failed, err: %v", err)}for {// 接收服务端返回的流式数据,服务端通过send发送,客户端通过Recv接受,当收到io.EOF或错误时退出res, err := stream.Recv()if err == io.EOF {break}if err != nil {log.Fatalf("c.LotsOfReplies failed, err: %v", err)}log.Printf("got reply: %q\n", res.GetReply())}
}
注意这里服务端是通过stream.Send(data)发送数据的 客户端是通过stream.Recv()接受数据的
执行
注意此时的流的流向为服务端流向客户端 所以称之为服务端流式调用
服务端实时发送数据到流中,客户端实时监听流中有无数据,当监听到没有数据了流关闭,客户端关闭
场景举例:
- 股票行情推送:客户端请求某股票代码后,服务端持续推送实时价格波动数据
- 物联网设备监控:服务端持续推送温度传感器、GPS定位等实时采集数据流
- 在线游戏状态同步:服务端向玩家客户端持续推送其他玩家的位置和动作数据
- 视频流传输:客户端请求视频文件后,服务端分块传输视频流数据
- 日志文件传输:服务端将大型日志文件拆分为多个数据包流式传输
- 数据库查询结果集传输:当查询结果包含百万级记录时,服务端分批次流式返回数据
客户端流式调用
在客户端和服务端的proto文件依次增加如下程序
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);
重新生成代码
重新go build
服务端增加LotsOfGreetings实现
func (s *server) LotsOfGreetings(stream pb.Greeter_LotsOfGreetingsServer) error {reply := "你好:"for {// 接收客户端发来的流式数据res, err := stream.Recv()if err == io.EOF {// 最终统一回复return stream.SendAndClose(&pb.HelloResponse{Reply: reply,})}if err != nil {return err}reply += res.GetName()}
}
客户端增加LotsOfGreetings实现
func runLotsOfGreeting(c pb.GreeterClient) {ctx, cancel := context.WithTimeout(context.Background(), time.Second)defer cancel()// 客户端流式RPCstream, err := c.LotsOfGreetings(ctx)if err != nil {log.Fatalf("c.LotsOfGreetings failed, err: %v", err)}names := []string{"风清扬,", "扫地僧,", "无嗔大师"}for _, name := range names {// 发送流式数据err := stream.Send(&pb.HelloRequest{Name: name})if err != nil {log.Fatalf("c.LotsOfGreetings stream.Send(%v) failed, err: %v", name, err)}}res, err := stream.CloseAndRecv()if err != nil {log.Fatalf("c.LotsOfGreetings failed: %v", err)}log.Printf("got reply: %v", res.GetReply())
}
这里的调用和服务端流式调用反过来了
流式数据由客户端进行发送多次数据stream.Send,客户端统一做接受stream.Recv()
执行
场景举例:
- 日志聚合系统:多个客户端程序持续发送日志片段,服务端进行合并存储并返回写入状态
- 图片分块上传:移动端将大图拆分为多个数据包流式传输,服务端完成重组后返回MD5校验
- 直播推流场景:客户端分片上传视频流,服务端转码后返回转码成功响应
双向流式调用
在客户端和服务端的proto文件中加上如下程序
// 双向流式数据
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse);
重新生成代码
重新go build
服务端增加BidiHello实现
func (s *server) BidiHello(stream pb.Greeter_BidiHelloServer) error {for {// 接收流式请求in, err := stream.Recv()if err == io.EOF {return nil}if err != nil {return err}reply := in.GetName() + "收到了你的问候,祝你生活愉快!" // 对收到的数据做些处理// 返回流式响应if err := stream.Send(&pb.HelloResponse{Reply: reply}); err != nil {return err}}
}
客户端增加BidiHello实现
func runBidiHello(c pb.GreeterClient) {ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)defer cancel()// 双向流模式stream, err := c.BidiHello(ctx)if err != nil {log.Fatalf("c.BidiHello failed, err: %v", err)}waitc := make(chan struct{})go func() {for {// 接收服务端返回的响应in, err := stream.Recv()if err == io.EOF {// read done.close(waitc)return}if err != nil {log.Fatalf("c.BidiHello stream.Recv() failed, err: %v", err)}fmt.Printf("回答:%s\n", in.GetReply())}}()// 从标准输入获取用户输入reader := bufio.NewReader(os.Stdin) // 从标准输入生成读对象for {cmd, _ := reader.ReadString('\n') // 读到换行cmd = strings.TrimSpace(cmd)if len(cmd) == 0 {continue}if strings.ToUpper(cmd) == "QUIT" {break}// 将获取到的数据发送至服务端if err := stream.Send(&pb.HelloRequest{Name: cmd}); err != nil {log.Fatalf("c.BidiHello stream.Send(%v) failed: %v", cmd, err)}}stream.CloseSend()<-waitc
}
main函数调用
runBidiHello(c)
执行
这里的流式数据传输是双向的
调用步骤
1,客户端建立流式调用
stream, err := c.BidiHello(ctx)
2,客户端发送终端输入的指令进行send发送流式数据给服务端
if err := stream.Send(&pb.HelloRequest{Name: cmd}); err != nil {log.Fatalf("c.BidiHello stream.Send(%v) failed: %v", cmd, err)}
3,服务端循环接受客户端的流式响应数据
in, err := stream.Recv()
4,服务端对于客户端的做加工处理和返回
reply := in.GetName() + "收到了你的问候,祝你生活愉快!" // 对收到的数据做些处理// 返回流式响应if err := stream.Send(&pb.HelloResponse{Reply: reply}); err != nil {return err}
gRPC结合gin开发用户注册接口
需求:使用gin+grpc+gorm实现用户注册接口,分模块微服务设计
项目总目录
douyin
├─ 📁gateway //网关层
│ ├─ 📁cmd
│ │ └─ 📄main.go //网关主入口
│ ├─ 📁internal
│ │ ├─ 📁controller //网关控制器
│ │ │ └─ 📄user.go
│ │ ├─ 📁grpc_client //grpc客户端
│ │ │ └─ 📄grpc_client.go
│ │ └─ 📁router
│ │ └─ 📄router.go //路由
│ ├─ 📄go.mod
│ └─ 📄go.sum
├─ 📁user_service //user_service微服务模块
│ ├─ 📁internal
│ │ ├─ 📁config //配置类如sql redis等
│ │ │ ├─ 📄application.yaml
│ │ │ └─ 📄config.go
│ │ ├─ 📁handler //grpc处理
│ │ │ └─ 📄user.go
│ │ ├─ 📁repository //db repository
│ │ │ └─ 📄user.go
│ │ ├─ 📁service //service层
│ │ │ └─ 📄user.go
│ │ └─ 📁store // db层
│ │ └─ 📁model
│ │ ├─ 📄model.go
│ │ └─ 📄user.go
│ ├─ 📁proto //grpc自动生成proto
│ │ ├─ 📁pb
│ │ │ └─ 📄user.proto
│ │ ├─ 📄user.pb.go
│ │ └─ 📄user_grpc.pb.go
│ ├─ 📄go.mod
│ ├─ 📄go.sum
│ └─ 📄main.go //grpc启动主入口
├─ 📄go.mod
└─ 📄go.sum
首先执行go mod init 项目名称
导入go.mod依赖
网关层
在gateway项目下执行
go get github.com/gin-gonic/gin
定义网关控制器函数internal/controller/user.go
package controllerimport ("gateway/internal/grpc_client""net/http""github.com/gin-gonic/gin"
)type UserController struct {userClient *grpc_client.UserClient
}//提供给grpc调用客户端
func NewUserController(client *grpc_client.UserClient) *UserController {return &UserController{userClient: client}
}func (c *UserController) Register(ctx *gin.Context) {type Request struct {Username string `json:"username" binding:"required,min=4,max=20"`Password string `json:"password" binding:"required,min=6,max=20"`Phone string `json:"phone" binding:"required"`}var req Request//使用json格式传输数据if err := ctx.ShouldBindJSON(&req); err != nil {ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})return}// 调用gRPC服务resp, err := c.userClient.Register(ctx, req.Username, req.Password, req.Phone)if err != nil {ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})return}ctx.JSON(http.StatusOK, gin.H{"user_id": resp,})
}
定义grpc客户端
package grpc_clientimport ("context""user_service/proto" // 确保proto生成的代码路径正确"google.golang.org/grpc""google.golang.org/grpc/credentials/insecure"
)type UserClient struct {conn *grpc.ClientConnclient proto.UserServiceClient // 假设proto生成的接口为UserServiceClient
}func NewUserClient(serverAddr string) (*UserClient, error) {// 建立gRPC连接conn, err := grpc.Dial(serverAddr,grpc.WithTransportCredentials(insecure.NewCredentials()), // 禁用TLSgrpc.WithBlock(), // 阻塞直到连接成功)if err != nil {return nil, err}return &UserClient{conn: conn,client: proto.NewUserServiceClient(conn),}, nil
}// Register 调用gRPC服务注册方法
func (c *UserClient) Register(ctx context.Context,username, password, phone string,
) (uint32, error) {resp, err := c.client.Register(ctx, &proto.UserRequest{Username: username,Password: password,Phone: phone,})if err != nil {return 0, err}return resp.UserId, nil
}// Close 关闭连接
func (c *UserClient) Close() error {return c.conn.Close()
}
网关层执行主函数cmd/main.go
package mainimport (// 控制器所在路径"gateway/internal/controller""gateway/internal/grpc_client""log""os""os/signal""syscall""github.com/gin-gonic/gin"
)func main() {// 1. 初始化gRPC客户端userClient, err := grpc_client.NewUserClient("localhost:50051") // 替换为实际地址if err != nil {log.Fatalf("Failed to create gRPC client: %v", err)} else {log.Println("gRPC client created successfully")}defer userClient.Close()// 2. 创建控制器userController := controller.NewUserController(userClient)// 3. 配置Gin路由router := gin.Default()api := router.Group("/api/v1"){api.POST("/register", userController.Register)}// 4. 启动HTTP服务器router.Run("127.0.0.1:8089") // listen and serve on 0.0.0.0:8080// go func() {// if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {// log.Fatalf("Server error: %v", err)// }// }()// 5. 优雅关闭quit := make(chan os.Signal, 1)signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)<-quitlog.Println("Shutting down server...")
}
注意gateway网关和user_service为两个独立的服务模块,而且gateway依赖user_service,需在网关层的go.mod文件显示声明两者的依赖关系
require user_service v0.0.0 // 声明依赖replace user_service => ../user_service // 替换为本地路径(假设 user_service 和 gateway 是同级目录)
下载文件所需依赖
go mod tidy
grpc微服务用户模块user_service
config配置类
用于定义服务所需的各个中间件服务和依赖的配置等,可以类比成Java的springboot的application.yml文件
定义application.yml
#项目相关配置
app:name: douyindebug: true
#数据库相关配置
database:driver: mysqlhost: 127.0.0.1port: 3306username: rootdbname:库名password:密码
#redis相关配置
redis:host: 127.0.0.1port: 6379
定义config配置加载函数 config.go
这里采用go最佳配置工具 github.com/spf13/viper
package configimport ("fmt""github.com/fsnotify/fsnotify""github.com/spf13/viper"
)// InitConfig 初始化配置文件
func InitConfig() {viper.AddConfigPath("./internal/config")viper.AddConfigPath(".") //多路径查找viper.SetConfigName("application")viper.SetConfigType("yaml")if err := viper.ReadInConfig(); err != nil {fmt.Printf("Failed to read config file: %v\nSearch paths: %v\n",err, viper.ConfigFileUsed())panic(err)}//监控并重新读取配置文件viper.WatchConfig()viper.OnConfigChange(func(e fsnotify.Event) {// 配置文件发生变更之后会调用的回调函数fmt.Println("Config file changed:", e.Name)})
}
核心处理函数internal/handler/user.go
这里相当于mvc模式三层架构的控制器层,整体grpc的三层调用顺序为handler->service->repositry
package handlerimport ("context""user_service/internal/service""user_service/proto""google.golang.org/grpc/codes""google.golang.org/grpc/status"
)type UserHandler struct {service *service.UserServiceproto.UnimplementedUserServiceServer
}func NewUserHandler(service *service.UserService) *UserHandler {return &UserHandler{service: service}
}func (h *UserHandler) Register(ctx context.Context, req *proto.UserRequest) (*proto.Response, error) {res, err := h.service.Register(ctx, req)if err != nil {if isDuplicateError(err) {return nil, status.Error(codes.AlreadyExists, "用户已存在")}return nil, status.Error(codes.Internal, err.Error())}return &proto.Response{Code: 200,UserId: uint32(res.UserId),Message: "注册成功",}, nil
}func isDuplicateError(err error) bool {// 根据具体数据库错误判断return true
}
service服务 service/user.go
package serviceimport ("context"repository "user_service/internal/repostry""user_service/internal/store/model""user_service/proto"
)type UserService struct {proto.UnimplementedUserServiceServer // 关键!必须嵌入repo *repository.UserRepository
}func NewUserService(repo *repository.UserRepository) *UserService {return &UserService{repo: repo}
}func (s *UserService) Register(ctx context.Context,req *proto.UserRequest,
) (*proto.Response, error) {// 实现具体注册逻辑// 例如:user, err := s.repo.Create(&model.User{Username: req.Username,Password: req.Password,Phone: req.Phone,})if err != nil {return nil, err}return &proto.Response{UserId: user.Id}, nil// 示例返回(替换为实际逻辑)// return &proto.Response{UserId: 1}, nil
}
repositry层repositry/user.go
package repositoryimport ("log""user_service/internal/store/model""github.com/jinzhu/gorm"
)type UserRepository struct {db *gorm.DB
}func NewUserRepository(db *gorm.DB) *UserRepository {return &UserRepository{db: db}
}func (r *UserRepository) Create(user *model.User) (*model.User, error) {if err := r.db.Create(user).Error; err != nil {return nil, err}log.Printf("Created user ID: %d", user.Id) // 插入后打印 IDreturn user, nil
}
数据库驱动加载 store/model/model.go
package modelimport ("fmt""log"_ "github.com/go-sql-driver/mysql""github.com/jinzhu/gorm""github.com/spf13/viper"
)// Db 用来承接db变量
var Db *gorm.DB// InitDb 初始化数据库连接
func InitDb() *gorm.DB {var (Username = viper.GetString("database.username")Password = viper.GetString("database.password")Host = viper.GetString("database.host")Port = viper.GetInt("database.port")DbName = viper.GetString("database.dbname"))dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8&parseTime=True&loc=Local", Username, Password, Host, Port, DbName)fmt.Println(Username)db, err := gorm.Open("mysql", dsn)if err != nil {log.Fatal("数据库连接失败,报错信息" + err.Error())}// 设置连接池,空闲连接db.DB().SetMaxIdleConns(50)// 打开链接db.DB().SetMaxOpenConns(100)// 表明禁用后缀加sdb.SingularTable(true)// 启用Logger,显示详细日志db.LogMode(viper.GetBool("app.debug"))return db
}
数据库映射实体类store/model/user.go
package modelimport ("time""gorm.io/gorm"
)type User struct {Id uint32 `gorm:"primaryKey;autoIncrement"`Username string `gorm:"uniqueIndex;size:64;not null"`Password string `gorm:"size:128;not null"`Phone string `gorm:"size:128;not null"`CreatedAt time.TimeUpdatedAt time.Time
}func (User) TableName() string {return "user_db"
}// Message模型(shared/model/message.go)
type Message struct {gorm.ModelContent string `gorm:"type:text;not null"`SenderID uint `gorm:"index:idx_sender"`ReceiverID uint `gorm:"index:idx_receiver"`
}
gprc proto文件自动生成代码定义
syntax = "proto3";
package user;
option go_package = "internal/proto"; // 完整 Go 导入路径:ml-citation{ref="1,2" data="citationList"}service UserService {rpc Register(UserRequest) returns (Response);
}message UserRequest {string username = 1;string password = 2;string email = 3;
}message Response {int32 code = 1;string message = 2;uint32 user_id = 3;
}
执行生成grpc代码
protoc --go_out=. --go-grpc_out=. internal/proto/pb/user.proto
生成的grpc的代码如下
定义grpc入口主函数
package mainimport ("log""net""user_service/internal/config"repository "user_service/internal/repostry""user_service/internal/service""user_service/internal/store/model""user_service/proto""google.golang.org/grpc"
)// 初始化配置文件
func init() {config.InitConfig()
}
func main() {// 初始化数据库db := model.InitDb()defer model.Db.Close()// 创建服务实例userRepo := repository.NewUserRepository(db)userService := service.NewUserService(userRepo)grpcServer := grpc.NewServer()初始化数据库连接后执行迁移db.AutoMigrate(&model.User{}, &model.Message{})// 注册gRPC服务proto.RegisterUserServiceServer(grpcServer, userService)// 启动服务lis, err := net.Listen("tcp", ":50051")if err != nil {log.Fatal(err)}log.Println("gRPC服务启动在 :50051")if err := grpcServer.Serve(lis); err != nil {log.Fatal(err)}
}
执行go mod tidy下载相关依赖
db表为 user_db
表结构如下
create table kanyuServer.user_db
(id bigint unsigned auto_increment comment '主键'primary key,phone varchar(11) not null comment '手机号码',password varchar(128) default '' null comment '密码,加密存储',user_name varchar(32) default '' null comment '昵称,默认是用户id',create_time timestamp default CURRENT_TIMESTAMP not null comment '创建时间',update_time timestamp default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',avatar varchar(255) default 'https://picsum.photos/60' null,constraint uniqe_key_phoneunique (phone)
);
执行
grpc服务启动
go run main.go
执行成功可以看到如下日志
网关服务启动
执行成功可以看到如下日志
请求注册接口/api/v1/register
返回
执行到这里则grpc整合gin的例子成功完成了
参考
李文周go教程
相关文章:
gRPC学习笔记记录以及整合gin开发
gprc基础 前置环境准备 grpc下载 项目目录下执行 go get google.golang.org/grpclatestProtocol Buffers v3 https://github.com/protocolbuffers/protobuf/releases/download/v3.20.1/protoc-3.20.1-linux-x86_64.zip go语言插件: go install google.golang.…...
Linux diff 命令使用详解
简介 Linux 中的 diff 命令用于逐行比较文件。它以各种格式报告差异,广泛应用于脚本编写、开发和补丁生成。 基础语法 diff [OPTION]... FILES常用选项 -i:忽略大小写 -u:打印输出时不包含任何多余的上下文行 -c:输出不同行周…...
非对称加密算法(RSA、ECC、SM2)——密码学基础
对称加密算法(AES、ChaCha20和SM4)Python实现——密码学基础(Python出现No module named “Crypto” 解决方案) 这篇的续篇,因此实践部分少些; 文章目录 一、非对称加密算法基础二、RSA算法2.1 RSA原理与数学基础2.2 RSA密钥长度…...
【安装指南】Chat2DB-集成了AI功能的数据库管理工具
一、Chat2DB 的介绍 Chat2DB 是一款开源的、AI 驱动的数据库工具和 SQL 客户端,提供现代化的图形界面,支持 MySQL、Oracle、PostgreSQL、DB2、SQL Server、SQLite、H2、ClickHouse、BigQuery 等多种数据库。它旨在简化数据库管理、SQL 查询编写、报表生…...
【C++】认识map和set
目录 前言: 一:认识map和set 二:map和set的使用 1.set的使用 2.map的使用 三:map的insert方法返回值 四:map的[ ]的使用 五:multiset和multimap 六:map和set的底层数据结构 七&#x…...
LWIP带freeRTOS系统移植笔记
以正点原子学习视频为基础的文章 LWIP带freeRTOS系统移植 准备资料/工程 1、lwIP例程1 lwIP裸机移植 工程 , 作为基础工程 改名为LWIP_freeRTOS_yizhi工程 2、lwIP例程6 lwIP_FreeRTOS移植 工程 3、freeRTO源码 打开https://www.freertos.org/网址下载…...
【MinerU技术原理深度解析】大模型时代的文档解析革命
目录 一、MinerU概述 获取MinerU 二、核心功能与技术亮点 1. 多模态解析能力 2. 高效预处理能力 3. 多场景适配性 4. API服务 三、技术架构解析 3.1 概述 1. 模块化处理流程 2. 关键模型与技术 3.2 核心组件技术原理 3.2.1 布局检测(Layout Detection) 3.2.2 公式…...
rabbitMQ如何确保消息不会丢失
rabbitmq消息丢失的三种情况 生产者将消息发送到RabbitMQ的过程中时,消息丢失。消息发送到RabbitMQ,还未被持久化就丢失了数据。消费者接收到消息,还未处理,比如服务宕机导致消息丢失。 解决方案 生产者发送过程中,…...
数字智慧方案5970丨智慧农业大数据服务建设方案(69页PPT)(文末有下载方式)
详细资料请看本解读文章的最后内容。 资料解读:智慧农业大数据服务建设方案 在当今数字化时代,农业领域也正经历着深刻变革,智慧农业大数据服务建设方案应运而生。这一方案对推动农业现代化进程意义非凡,下面让我们深入剖析其核心…...
英一真题阅读单词笔记 22-23年
2022年真题阅读单词 2022 年 Text 1 第一段 1 complain [kəmˈpleɪn] v. 抱怨,投诉;诉说(病痛) 2 plastic [ˈplstɪk] n. 塑料;信用卡 a. 造型的,塑造的;塑料制的 3 durable [ˈd…...
Java大师成长计划之第10天:锁与原子操作
📢 友情提示: 本文由银河易创AI(https://ai.eaigx.com)平台gpt-4o-mini模型辅助创作完成,旨在提供灵感参考与技术分享,文中关键数据、代码与结论建议通过官方渠道验证。 在多线程编程中,锁与原子…...
2025大模型安全研究十大框架合集(10份)
2025大模型安全研究十大框架合集的详细介绍: Anthropic AI信任研究框架 Anthropic于2024年10月更新的《安全责任扩展政策》(RSP),提出了一个灵活的动态AI风险治理框架。该框架规定当AI模型达到特定能力时,将自动升级安全措施,如…...
溯因推理思维——AI与思维模型【92】
一、定义 溯因推理思维模型是一种从结果出发,通过分析、推测和验证,寻找导致该结果的可能原因的思维方式。它试图在已知的现象或结果基础上,逆向追溯可能的原因,构建合理的解释框架,以理解事物的本质和内在机制。 二、由来 溯因推理的思想可以追溯到古希腊哲学家亚里士…...
系统架构设计师:设计模式——结构型设计模式
一、结构型设计模式 结构型设计模式涉及如何组合类和对象以获得更大的结构。结构型类模式采用继承机制来组合接口或实现。一个简单的例子是采用多重继承方法将两个以上的类组合成一个类,结果这个类包含了所有父类的性质。 这一模式尤其有助于多个独立开发的类库协…...
接口测试实战指南:从入门到精通的质量保障之道
为什么接口测试如此重要? 在当今快速迭代的软件开发环境中,接口测试已成为质量保障体系中不可或缺的一环。据统计,有效的接口测试可以发现约70%的系统缺陷,同时能将测试效率提升3-5倍。本指南将从实战角度出发,系统性…...
对第三方软件开展安全测评,如何保障其安全使用?
对第三方软件开展安全测评,能够精准找出软件存在的各类安全隐患,进而为软件的安全使用给予保障。此次会从漏洞发现、风险评估、测试环境等多个方面进行具体说明。 漏洞发现情况 在测评过程中,我们借助专业技术与工具,对第三方软…...
计算方法实验四 解线性方程组的间接方法
【实验性质】 综合性实验。 【实验目的】 掌握迭代法求解线性方程组。 【实验内容】 应用雅可比迭代法和Gauss-Sediel迭代法求解下方程组: 【理论基础】 线性方程组的数值解法分直接算法和迭代算法。迭代法将方程组的求解转化为构造一个向量序列&…...
Qt 中基于 QTableView + QSqlTableModel 的分页搜索与数据管理实现
Qt 中基于 QTableView QSqlTableModel 的分页搜索与数据管理实现 一、组件说明 QTableView:一个基于模型的表格视图控件,支持排序、选择、委托自定义。QSqlTableModel:与数据库表直接绑定的模型类,可用于展示和编辑数据库表数据…...
云计算-容器云-服务网格Bookinfo
服务网格:创建 Ingress Gateway 将 Bookinfo 应用部署到 default 命名空间下,请为 Bookinfo 应用创建一个网 关,使外部可以访问 Bookinfo 应用。 上传ServiceMesh.tar.gz包 [rootk8s-master-node1 ~]# tar -zxvf ServiceMesh.tar.gz [rootk…...
PostgreSQL自定义函数
自定义函数 基本语法 //建一个名字为function_name的自定义函数create or replace function function_name() returns data_type as //returns 返回一个data_type数据类型的结果;data_type 是返回的字段的类型;$$ //固定写法......//方法体$$ LANGUAGE …...
学习记录:DAY22
我的重生开发之旅:优化DI容器,git提交规范,AOP处理器,锁与并发安全 前言 我重生了,重生到了五一开始的一天。上一世,我天天摆烂,最后惨遭实习生优化。这一世,我要好好内卷… 今天的…...
HarmonyOS NEXT第一课——HarmonyOS介绍
一、什么是HarmonyOS 万物互联时代应用开发的机遇、挑战和趋势 随着万物互联时代的开启,应用的设备底座将从几十亿手机扩展到数百亿IoT设备。全新的全场景设备体验,正深入改变消费者的使用习惯。 同时应用开发者也面临设备底座从手机单设备到全场景多设…...
数据库系统概论|第五章:数据库完整性—课程笔记1
前言 在前文介绍完数据库标准语言SQL之后,大家已经基本上掌握了关于数据库编程的基本操作,那我们今天将顺承介绍关于数据库完整性的介绍,数据库的完整性是指数据的正确性和相容性。数据的完整性是为了防止数据库中存在不符合语义的数据&…...
开源无人机地面站QGroundControl安卓界面美化与逻辑优化实战
QGroundControl作为开源无人机地面站软件,其安卓客户端界面美化与逻辑优化是提升用户体验的重要工程。 通过Qt框架的界面重构和代码逻辑优化,可以实现视觉升级与性能提升的双重目标。本文将系统讲解QGC安卓客户端的二次开发全流程,包括开发环境搭建、界面视觉升级、多分辨率…...
工作记录 2017-12-12 + 在IIS下发布wordpress
工作记录 2017-12-12 序号 工作 相关人员 1 修改邮件上的问题。 更新RD服务器。 在IIS下发布wordpress。 郝 服务器更新 RD服务器更新了,更新的文件放在190的D:\Temp\CHTeam\fnehr_update_20171212\下了。 数据库更新: 数据库没有更新 更新的文件…...
BBR 之 ProbeRTT 新改
早在 1981 年,Jaffe 在 Flow Control Power is Nondecentralizable 中就给出过论证,测量 maxbw 必然引入队列,而获得 minrtt 时带宽必然欠载,这确定了后面 30 年的拥塞控制算法基调,但 BBR 在 35 年后非常聪明地在两者…...
[创业之路-354]:农业文明到智能纪元:四次工业革命下的人类迁徙与价值重构
农业文明到智能纪元:四次工业革命下的人类迁徙与价值重构 从游牧到定居,从蒸汽轰鸣到算法洪流,人类文明的每一次跨越都伴随着生产关系的剧烈震荡。四次工业革命的浪潮不仅重塑了物质世界的生产方式,更将人类推向了身份认同与存在…...
敏感词 v0.25.0 新特性之 wordCheck 策略支持用户自定义
开源项目 敏感词核心 https://github.com/houbb/sensitive-word 敏感词控台 https://github.com/houbb/sensitive-word-admin 版本特性 大家好,我是老马。 敏感词一开始了内置了多种检验策略,但是很多用户在使用的过程中希望可以自定义策略。 所以 v0…...
从0到上线,CodeBuddy 如何帮我快速构建旅游 App?
引言 腾讯云AI代码助手之前就改成了CodeBuddy我相信这也是在为后期做准备。那么这篇文章会对CodeBuddy进行比较详细的介绍,并一起来上手实战,感受一下实际开发中这款插件能带给我们多少的便利。本篇文章是一边写一边进行测试,并不是测试完之…...
微信小程序 自定义组件 标签管理
环境 小程序环境: 微信开发者工具:RC 1.06.2503281 win32-x64 基础运行库:3.8.1 概述 基础功能 标签增删改查:支持添加/删除单个标签、批量删除、重置默认标签 数据展示:通过对话框展示结构化数据并支持复制 动…...
从 Eclipse Papyrus / XText 转向.NET —— SCADE MBD技术的演化
从KPN[1]的萌芽开始,到SCADE的推出[2],再到Scade 6的技术更迭[3],SCADE 基于模型的开发技术已经历许多。现在,Scade One 已开启全新的探索 —— 从 Eclipse Papyrus / XText 转向.NET 8跨平台应用。 [1]: KPN, Kahn进程网络 (197…...
【学习笔记】机器学习(Machine Learning) | 第五章(2)| 分类与逻辑回归
机器学习(Machine Learning) 简要声明 基于吴恩达教授(Andrew Ng)课程视频 BiliBili课程资源 文章目录 机器学习(Machine Learning)简要声明 二、决策边界决策边界的数学表达线性决策边界示例非线性决策边界非线性决策边界的示例…...
python 常用web开发框架及使用示例
Python常用Web开发框架及使用示例 Python拥有丰富的Web开发框架生态系统,以下是主流框架及其使用示例: 一、Flask - 轻量级框架 安装 pip install flask 基础示例 from flask import Flask, request, jsonifyapp Flask(__name__)app.route(/) def…...
[ Qt ] | 第一个Qt程序
1. 创建Qt项目 我们打开Qt Create工具,左上角“文件”,新建文件。 --- --- --- --- 这个是我们的APP“走出国门”的时候,要关注的,这里就不说了。 后面这两个直接默认,下一步就行~~。 2. 项目默认内容 下面就是Qt C…...
react + antd 实现后台管理系统
文章目录 完整路由搭建Layout 和 Aside组件引入 AntdAside组件实现 项目效果图 项目完整代码地址 https://gitee.com/lyh1999/react-back-management 项目完整代码地址 react依赖安装 最好采用yarn 安装 react-router 安装依赖 配置路由 history模式 / // src/router/…...
vue3+ts项目 配置vue-router
安装vue-router pnpm install vue-router配置 1.src/router/index.ts文件下的内容 import type { App } from vue import type { RouteRecordRaw } from vue-router import { createRouter, createWebHistory } from vue-router import remainingRouter from ./modules/remai…...
MySQL基本查询(二)
文章目录 UpdateDelete插入查询结果(select insert)聚合函数分组聚合统计 Update 1. 语法: set后面加列属性或者表达式 UPDATE table_name SET column expr [, column expr …][WHERE …] [ORDER BY …] [LIMIT …] 案例 将孙悟空同学的…...
MySQL:联合查询
目录 一、笛卡尔积 二、内连接 三、外连接 (1)左外连接 (2)右外连接 (3)全外连接 四、自连接 五、子查询 (1)单行子查询 (2)多行子查询 &…...
[算法学习]——通过RMQ与dfs序实现O(1)求LCA(含封装板子)
每周五篇博客:(3/5) 碎碎念 其实不是我想多水一篇博客,本来这篇是欧拉序的博客,结果dfs序也是可以O1求lca的,而且常数更优,结果就变成这样了。。。 前置知识 [算法学习]——dfs序 思想 分…...
复刻低成本机械臂 SO-ARM100 舵机配置篇(WSL)
视频讲解: 复刻低成本机械臂 SO-ARM100 舵机配置篇(WSL) 飞特舵机 组装之前需要配置舵机的ID,如下的网址为舵机的资料,实际上用不到,但可以mark在这里 Software-深圳飞特模型有限公司 User Guide里面可以…...
聊一聊接口测试更侧重于哪方面的验证
目录 一、功能性验证 输入与输出正确性 参数校验 业务逻辑覆盖 二、数据一致性验证 数据格式规范 数据完整性 数据类型与范围 三、异常场景验证 容错能力测试 边界条件覆盖 错误码与信息清晰度 四、安全与权限验证 身份认证 数据安全 防攻击能力 五、性能与可…...
【网络安全实验】SSL协议的应用
目录 一、SSL协议介绍 2.功能与特点 1)数据加密 2)身份验证 3)数据完整性校验 3.SSL的工作流程(握手过程) 1)客户端问候(ClientHello) 2)服务器响应(…...
测试——用例篇
目录 1. 测试用例 1.1 概念 2. 设计测试用例的万能公式 2.1 常规思考逆向思维发散性思维 2.2 万能公式 3. 设计测试用例例的方法 3.1 基于需求的设计方法 编辑 3.2 具体的设计方法 3.2.1 等价类 3.2.2 边界值 3.2.3 正交法 3.2.4 判定表法 3.2.5 场景法 3.2.6…...
计算机视觉技术的发展历程
计算机视觉技术的发展历程可以分为以下几个阶段: 早期探索阶段(1960s-1980s) 1960年代:计算机视觉的概念开始形成,研究者尝试让计算机识别和理解图像,主要集中在基础的图像处理,如边缘检测和特…...
docker 官方:在 alpine 上安装 python 的方法
在 alpine 上安装 python 的方法在 alpine 上安装 python 的方法: # alpine 官方 apk add python3 # docker 官方 docker pull python:3.11-alpine # 第三方 docker run --rm frolvlad/alpine-python3 python3 -c print("Hello World") # 编译安装 略 要点…...
mescroll.js 是在 H5端 运行的下拉刷新和上拉加载插件
1. mescroll的uni版本, 是专门用在uni-app的下拉刷新和上拉加载的组件, 支持一套代码编译到iOS、Android、H5、小程序等多个平台 2. mescroll的uni版本, 继承了mescroll.js的实用功能: 自动处理分页, 自动控制无数据, 空布局提示, 回到顶部按钮 .. 3. mescroll的uni版本, 丰富的…...
openEuler 22.03 安装 Mysql 5.7,RPM 在线安装
目录 一、检查系统是否安装其他版本Mariadb数据库二、安装 MySQL三、配置 MySQL四、修改默认存储路径五、开放防火墙端口六、数据备份七、生产环境优化八、常用命令 一、检查系统是否安装其他版本Mariadb数据库 # 查看已安装的 Mariadb 数据库版本 [rootopeneuler ~]# rpm -qa…...
云原生后端架构的挑战与应对策略
📝个人主页🌹:慌ZHANG-CSDN博客 🌹🌹期待您的关注 🌹🌹 随着云计算、容器化以及微服务等技术的快速发展,云原生架构已经成为现代软件开发和运维的主流趋势。企业通过构建云原生后端系统,能够实现灵活的资源管理、快速的应用迭代和高效的系统扩展。然而,尽管云原…...
第十六届蓝桥杯 2025 C/C++组 客流量上限
目录 题目: 题目描述: 题目链接: 思路: 打表找规律: 核心思路: 思路详解: 得到答案的方式: 按计算器: 暴力求解代码: 快速幂代码: 位运…...
LeetCode算法题 (移除链表元素)Day15!!!C/C++
https://leetcode.cn/problems/remove-linked-list-elements/description/ 一、题目分析 给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val val 的节点,并返回 新的头节点 。 今天的题目非常好理解,也就是要删除…...