• go-zero单体服务使用泛型简化注册Handler路由


    🚀 优质资源分享 🚀

    学习路线指引(点击解锁)知识定位人群定位
    🧡 Python实战微信订餐小程序 🧡进阶级本课程是python flask+微信小程序的完美结合,从项目搭建到腾讯云部署上线,打造一个全栈订餐系统。
    💛Python量化交易实战💛入门级手把手带你打造一个易扩展、更安全、效率更高的量化交易系统

    一、Golang环境安装及配置Go Module

    https://go-zero.dev/cn/docs/prepare/golang-install

    mac OS安装Go#

    • 下载并安装Go for Mac
    • 验证安装结果
    Copy Highlighter-hljs
    
    |  | $ go version |
    |  | go version go1.15.1 darwin/amd64 |
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    linux 安装Go#

    • 下载Go for Linux
    • 解压压缩包至/usr/local
    Copy Highlighter-hljs$ tar -C /usr/local -xzf go1.15.8.linux-amd64.tar.gz
    
    
    • 1
    • 2
    • 添加/usr/local/go/bin到环境变量
    Copy Highlighter-hljs
    
    |  | $ $HOME/.profile |
    |  | $ export PATH=$PATH:/usr/local/go/bin |
    |  | $ source $HOME/.profile |
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 验证安装结果
    Copy Highlighter-hljs
    
    |  | $ go version |
    |  | go version go1.15.1 linux/amd64 |
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    Windows安装Go#

    • 下载并安装Go for Windows
    • 验证安装结果
    Copy Highlighter-hljs
    
    |  | $ go version |
    |  | go version go1.15.1 windows/amd64 |
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    MODULE配置

    Go Module是Golang管理依赖性的方式,像Java中的Maven,Android中的Gradle类似。

    • 查看GO111MODULE开启情况
    Copy Highlighter-hljs
    
    |  | $ go env GO111MODULE |
    |  | on |
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 开启GO111MODULE,如果已开启(即执行go env GO111MODULE结果为on)请跳过。
    Copy Highlighter-hljs$ go env -w GO111MODULE="on"
    
    
    • 1
    • 2
    • 设置GOPROXY
    Copy Highlighter-hljs$ go env -w GOPROXY=https://goproxy.cn
    
    
    • 1
    • 2
    • 设置GOMODCACHE
    Copy Highlighter-hljs查看GOMODCACHE
    
    
    • 1
    • 2

    $ go env GOMODCACHE

    • 如果目录不为空或者/dev/null,请跳过。
    Copy Highlighter-hljsgo env -w GOMODCACHE=$GOPATH/pkg/mod
    
    
    • 1
    • 2

    二、Goctl 安装

    Goctl在go-zero项目开发着有着很大的作用,其可以有效的帮助开发者大大提高开发效率,减少代码的出错率,缩短业务开发的工作量,更多的Goctl的介绍请阅读Goctl介绍

    • 安装(mac&linux)
    Copy Highlighter-hljs
    
    |  | ### Go 1.15 及之前版本 |
    |  | GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/zeromicro/go-zero/tools/goctl@latest |
    |  |  |
    |  | ### Go 1.16 及以后版本 |
    |  | GOPROXY=https://goproxy.cn/,direct go install github.com/zeromicro/go-zero/tools/goctl@latest |
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 安装(windows)
    Copy Highlighter-hljsgo install github.com/zeromicro/go-zero/tools/goctl@latest
    
    
    • 1
    • 2
    • 环境变量检测(mac&linux)
      go get 下载编译后的二进制文件位于 $GOPATH/bin 目录下,要确保 $GOPATH/bin已经添加到环境变量。
    Copy Highlighter-hljssudo vim /etc/paths //添加环境变量
    
    
    • 1
    • 2

    在最后一行添加如下内容 //$GOPATH 为你本机上的文件地址

    Copy Highlighter-hljs$GOPATH/bin 
    
    
    • 1
    • 2
    • 安装结果验证
    Copy Highlighter-hljs
    
    |  | $ goctl -v |
    |  | goctl version 1.1.4 darwin/amd64 |
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    二、初始化go-zero

    Copy Highlighter-hljs
    
    |  | goctl api new greet |
    |  | cd greet |
    |  | go mod init |
    |  | go mod tidy |
    |  | go run greet.go -f etc/greet-api.yaml |
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 默认侦听在 8888 端口
      侦听端口可以在greet-api.yaml配置文件里修改,此时,可以通过 curl 请求,或者直接在浏览器中打开http://localhost:8888/from/you
    Copy Highlighter-hljs
    
    |  | $ curl -i http://localhost:8888/from/you |
    |  |  |
    |  | HTTP/1.1 200 OK |
    |  | Content-Type: application/json; charset=utf-8 |
    |  | Traceparent: 00-45fa9e7a7c505bad3a53a024e425ace9-eb5787234cf3e308-00 |
    |  | Date: Thu, 22 Oct 2020 14:03:18 GMT |
    |  | Content-Length: 14 |
    |  |  |
    |  | null |
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • greet服务的目录结构
    Copy Highlighter-hljs
    
    |  | $ tree greet |
    |  | greet |
    |  | ├── etc |
    |  | │ └── greet-api.yaml |
    |  | ├── greet.api |
    |  | ├── greet.go |
    |  | └── internal |
    |  |  ├── config |
    |  |  │ └── config.go |
    |  |  ├── handler |
    |  |  │ ├── greethandler.go |
    |  |  │ └── routes.go |
    |  |  ├── logic |
    |  |  │ └── greetlogic.go |
    |  |  ├── svc |
    |  |  │ └── servicecontext.go |
    |  |  └── types |
    |  |  └── types.go |
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    三、查看注册Handler路由流程

    • greet.go
    Copy Highlighter-hljs
    
    |  | var configFile = flag.String("f", "etc/greet-api.yaml", "the config file") |
    |  |  |
    |  | func main() { |
    |  |  flag.Parse() |
    |  |  |
    |  | var c config.Config |
    |  |  conf.MustLoad(*configFile, &c) |
    |  |  |
    |  |  server := rest.MustNewServer(c.RestConf) |
    |  | defer server.Stop() |
    |  | //上面的都是加载配置什么的 |
    |  |  ctx := svc.NewServiceContext(c) |
    |  |  handler.RegisterHandlers(server, ctx) //此方法是注册路由和路由映射Handler,重点在这里 |
    |  |  |
    |  |  fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port) |
    |  |  server.Start() |
    |  | } |
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • RegisterHandlers在internal\handler\routes.go
    Copy Highlighter-hljs
    
    |  | func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { |
    |  |  server.AddRoutes( //往rest.Server中添加路由 |
    |  | []rest.Route{ //路由数组 |
    |  |  { |
    |  |  Method: http.MethodGet, |
    |  |  Path: "/from/:name", //路由 |
    |  |  Handler: GreetHandler(serverCtx),//当前路由的处理Handler |
    |  |  }, |
    |  |  }, |
    |  |  ) |
    |  | } |
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • GreetHandler在internal\handler\greethandler.go
    Copy Highlighter-hljs
    
    |  | func GreetHandler(ctx *svc.ServiceContext) http.HandlerFunc { |
    |  | return func(w http.ResponseWriter, r *http.Request) { |
    |  | 1. var req types.Request |
    |  | 2. if err := httpx.Parse(r, &req); err != nil { //请求的错误判断,这个可以不用管 |
    |  | 3. httpx.Error(w, err) |
    |  | 4. return |
    |  | 5. } |
    |  |  |
    |  |  l := logic.NewGreetLogic(r.Context(), ctx) //GreetHandler处理函数将请求转发到了GreetLogic中,调用NewGreetLogic进行结构体的初始化 |
    |  |  resp, err := l.Greet(req) //然后调用Greet来进行处理请求,所以我们在GreetLogic.Greet方法中可以看到一句话// todo: add your logic here and delete this line |
    |  | if err != nil { |
    |  |  httpx.Error(w, err) |
    |  |  } else { |
    |  |  httpx.OkJson(w, resp) |
    |  |  } |
    |  |  } |
    |  | } |
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    四、对注册Handler路由进行简化

    项目文件的增加

    在路由注册时,我们如果服务越加越多,那么相对应的func xxxxHandler(ctx *svc.ServiceContext) http.HandlerFunc就要进行多次的添加,并且这个方法体内部1到5行是属于额外的重复添加
    例如:我们添加一个customlogic.go
    按照命名的正确和规范性,需要在internal\logic目录下添加customlogic.go文件,然后在internal\handler目录下添加customhandler.go文件,并且两个文件都添加相对应的结构体和函数等,最后在routes.go中再添加一次

    Copy Highlighter-hljs
    
    |  | { |
    |  |  Method: http.MethodGet, |
    |  |  Path: "/custom/:name", |
    |  |  Handler: CustomHandler(serverCtx), |
    |  | }, |
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    此时,我们的文件结构应该是这样

    Copy Highlighter-hljs
    
    |  | greet |
    |  | ├── etc |
    |  | │ └── greet-api.yaml |
    |  | ├── greet.api |
    |  | ├── greet.go |
    |  | └── internal |
    |  |  ├── config |
    |  |  │ └── config.go |
    |  |  ├── handler |
    |  |  │ ├── greethandler.go |
    |  |  │ ├── customhandler.go |
    |  |  │ ├── ... |
    |  |  │ └── routes.go |
    |  |  ├── logic |
    |  |  │ ├── greetlogic.go |
    |  |  │ ├── ... |
    |  |  │ └── customlogic.go |
    |  |  ├── svc |
    |  |  │ └── servicecontext.go |
    |  |  └── types |
    |  |  └── types.go |
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    当单体应用达到一定的数量级,handler和logic文件夹下将会同步增加很多的文件

    引入泛型概念

    自Go1.18开始,go开始使用泛型,泛型的广泛定义 :是一种把明确类型的工作推迟到创建对象或者调用方法的时候才去明确的特殊的类型。 也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,而这种参数类型可以用在 类、方法和接口 中,分别被称为 泛型类 、 泛型方法 、 泛型接口 。
    我们可以利用泛型,让在添加路由时就要固定死的Handler: GreetHandler(serverCtx)推迟到后面,去根据实际的Logic结构体去判断需要真正执行的logic.NewGreetLogic(r.Context(), ctx)初始化结构体和l.Greet(req)逻辑处理方法

    如何去做

    1. internal\logic下添加一个baselogic.go文件,参考Go泛型实战 | 如何在结构体中使用泛型
    Copy Highlighter-hljs
    
    |  | package logic |
    |  |  |
    |  | import ( |
    |  | "greet/internal/svc" |
    |  | "greet/internal/types" |
    |  | "net/http" |
    |  | ) |
    |  |  |
    |  | type BaseLogic interface { |
    |  |  any |
    |  |  Handler(req types.Request, w http.ResponseWriter, r *http.Request, svcCtx *svc.ServiceContext) //每一个结构体中必须要继承一下Handler方法,例如customlogic.go和greetlogic.go中的Handler方法 |
    |  | } |
    |  |  |
    |  | type logic[T BaseLogic] struct { |
    |  |  data T |
    |  | } |
    |  |  |
    |  | func New[T BaseLogic]() logic[T] { |
    |  |  c := logic[T]{} |
    |  | var ins T |
    |  |  c.data = ins |
    |  | return c |
    |  | } |
    |  | func (a *logic[T]) LogicHandler(req types.Request, w http.ResponseWriter, r *http.Request, svcCtx *svc.ServiceContext) { //作为一个中转处理方法,最终执行结构体的Handler |
    |  |  a.data.Handler(req, w, r, svcCtx) |
    |  | } |
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    1. greethandler.go文件修改成basehandler.go,注释掉之前的GreetHandler方法
    Copy Highlighter-hljs
    
    |  | package handler |
    |  |  |
    |  | import ( |
    |  | "net/http" |
    |  |  |
    |  | "greet/internal/logic" |
    |  | "greet/internal/svc" |
    |  | "greet/internal/types" |
    |  |  |
    |  | "github.com/zeromicro/go-zero/rest/httpx" |
    |  | ) |
    |  |  |
    |  | // func GreetHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { |
    |  | // return BaseHandlerFunc(svcCtx) |
    |  | // // return func(w http.ResponseWriter, r *http.Request) { |
    |  | // // var req types.Request |
    |  | // // if err := httpx.Parse(r, &req); err != nil { |
    |  | // // httpx.Error(w, err) |
    |  | // // return |
    |  | // // } |
    |  | // // l := logic.NewGreetLogic(r.Context(), svcCtx) |
    |  | // // resp, err := l.Greet(&req) |
    |  | // // if err != nil { |
    |  | // // httpx.Error(w, err) |
    |  | // // } else { |
    |  | // // httpx.OkJson(w, resp) |
    |  | // // } |
    |  | // // } |
    |  | // } |
    |  |  |
    |  | func BaseHandlerFunc[T logic.BaseLogic](svcCtx *svc.ServiceContext, t T) http.HandlerFunc { |
    |  | return func(w http.ResponseWriter, r *http.Request) { |
    |  | var req types.Request |
    |  | if err := httpx.Parse(r, &req); err != nil { |
    |  |  httpx.Error(w, err) |
    |  | return |
    |  |  } |
    |  | //通过泛型动态调用不同结构体的Handler方法 |
    |  |  cc := logic.New[T]() |
    |  |  cc.LogicHandler(req, w, r, svcCtx) |
    |  |  } |
    |  | } |
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    1. internal\logic\greetlogic.go中增加一个Handler方法
    Copy Highlighter-hljs
    
    |  | package logic |
    |  |  |
    |  | import ( |
    |  | "context" |
    |  | "net/http" |
    |  |  |
    |  | "greet/internal/svc" |
    |  | "greet/internal/types" |
    |  |  |
    |  | "github.com/zeromicro/go-zero/core/logx" |
    |  | "github.com/zeromicro/go-zero/rest/httpx" |
    |  | ) |
    |  |  |
    |  | type GreetLogic struct { |
    |  |  logx.Logger |
    |  |  ctx context.Context |
    |  |  svcCtx *svc.ServiceContext |
    |  | } |
    |  |  |
    |  | func NewGreetLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GreetLogic { |
    |  | return &GreetLogic{ |
    |  |  Logger: logx.WithContext(ctx), |
    |  |  ctx: ctx, |
    |  |  svcCtx: svcCtx, |
    |  |  } |
    |  | } |
    |  | func (a GreetLogic) Handler(req types.Request, w http.ResponseWriter, r *http.Request, svcCtx *svc.ServiceContext) { //新增方法 |
    |  |  l := NewGreetLogic(r.Context(), svcCtx) |
    |  |  resp, err := l.Greet(&req) |
    |  | if err != nil { |
    |  |  httpx.Error(w, err) |
    |  |  } else { |
    |  |  httpx.OkJson(w, resp) |
    |  |  } |
    |  | } |
    |  |  |
    |  | func (l *GreetLogic) Greet(req *types.Request) (resp *types.Response, err error) { |
    |  | // todo: add your logic here and delete this line |
    |  |  response := new(types.Response) |
    |  | if (*req).Name == "me" { |
    |  |  response.Message = "greetLogic: listen to me, thank you." |
    |  |  } else { |
    |  |  response.Message = "greetLogic: listen to you, thank me." |
    |  |  } |
    |  |  |
    |  | return response, nil |
    |  | } |
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    1. 然后修改internal\handler\routes.go下面的server.AddRoutes部分
    Copy Highlighter-hljs
    
    |  | func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { |
    |  |  server.AddRoutes( //往rest.Server中添加路由 |
    |  | []rest.Route{ //路由数组 |
    |  |  { |
    |  |  Method: http.MethodGet, |
    |  |  Path: "/from/:name", //路由 |
    |  |  Handler: BaseHandlerFunc(serverCtx,logic.GreetLogic{}), |
    |  |  }, |
    |  |  }, |
    |  |  ) |
    |  | } |
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    现在就大功告成了,我们启动一下

    Copy Highlighter-hljsgo run greet.go -f etc/greet-api.yaml
    
    
    • 1
    • 2

    然后在浏览器中请求一下http://localhost:8888/from/you

    验证一下新增api路由

    1. internal\logic下新增一个customlogic.go文件
    Copy Highlighter-hljs
    
    |  | package logic |
    |  |  |
    |  | import ( |
    |  | "context" |
    |  | "net/http" |
    |  |  |
    |  | "greet/internal/svc" |
    |  | "greet/internal/types" |
    |  |  |
    |  | "github.com/zeromicro/go-zero/core/logx" |
    |  | "github.com/zeromicro/go-zero/rest/httpx" |
    |  | ) |
    |  |  |
    |  | type CustomLogic struct { |
    |  |  logx.Logger |
    |  |  ctx context.Context |
    |  |  svcCtx *svc.ServiceContext |
    |  | } |
    |  |  |
    |  | func NewCustomLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CustomLogic { |
    |  | return &CustomLogic{ |
    |  |  Logger: logx.WithContext(ctx), |
    |  |  ctx: ctx, |
    |  |  svcCtx: svcCtx, |
    |  |  } |
    |  | } |
    |  |  |
    |  | func (a CustomLogic) Handler(req types.Request, w http.ResponseWriter, r *http.Request, svcCtx *svc.ServiceContext) { |
    |  |  l := NewCustomLogic(r.Context(), svcCtx) |
    |  |  resp, err := l.Custom(&req) |
    |  | if err != nil { |
    |  |  httpx.Error(w, err) |
    |  |  } else { |
    |  |  httpx.OkJson(w, resp) |
    |  |  } |
    |  | } |
    |  |  |
    |  | func (l *CustomLogic) Custom(req *types.Request) (resp *types.Response, err error) { //response.Message稍微修改了一下,便于区分 |
    |  | // todo: add your logic here and delete this line |
    |  |  response := new(types.Response) |
    |  | if (*req).Name == "me" { |
    |  |  response.Message = "customLogic: listen to me, thank you." |
    |  |  } else { |
    |  |  response.Message = "customLogic: listen to you, thank me." |
    |  |  } |
    |  |  |
    |  | return response, nil |
    |  | } |
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    1. 然后修改internal\handler\routes.go
    Copy Highlighter-hljs
    
    |  | func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { |
    |  |  server.AddRoutes( //往rest.Server中添加路由 |
    |  | []rest.Route{ //路由数组 |
    |  |  { |
    |  |  Method: http.MethodGet, |
    |  |  Path: "/from/:name", //路由 |
    |  |  Handler: BaseHandlerFunc(serverCtx,logic.GreetLogic{}), |
    |  |  }, |
    |  |  { |
    |  |  Method: http.MethodGet, |
    |  |  Path: "/to/:name", //路由 |
    |  |  Handler: BaseHandlerFunc(serverCtx,logic.CustomLogic{}), |
    |  |  }, |
    |  |  }, |
    |  |  ) |
    |  | } |
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    其他地方不需要修改
    我们启动一下

    Copy Highlighter-hljsgo run greet.go -f etc/greet-api.yaml
    
    
    • 1
    • 2

    然后在浏览器中请求一下http://localhost:8888/from/youhttp://localhost:8888/to/youhttp://localhost:8888/too/you

    现在,在添加新的logic做路由映射时,就可以直接简化掉添加xxxxhandler.go文件了,实际上是将这个Handler移动到了xxxxlogic.go中。

    新手,不喜轻喷

    本文代码放在

  • 相关阅读:
    家用办公主机需要多少钱?推荐主机选购攻略!!
    Python标准库glob模块详解
    Python课程设计之学生信息管理系统
    【IoT】产品认证:国密认证中的委托人、生产者、生产企业是什么意思?
    java-php-python-ssm-忻府区饭中有豆粮油销售系统-计算机毕业设计
    SpringSecurity(八)【RememberMe记住我】
    腾讯面试真题 | 没在我八股文列表里。。。
    OpenCV的石头检测~
    用Maloja创建音乐收听统计数据
    在实际工作中如何开展性能测试?
  • 原文地址:https://blog.csdn.net/qq_43479892/article/details/126026200