Go语言程序开发gRPC服务
对于一个Golang开发者来说,牢固扎实的基础是十分重要的,golang学习网就来带大家一点点的掌握基础知识点。今天本篇文章带大家了解《Go语言程序开发gRPC服务》,主要介绍了gRPC、服务,希望对大家的知识积累有所帮助,快点收藏起来吧,否则需要时就找不到了!
作为一名程序员,学就对了。
之前用 Python 写过一些 gRPC 服务,现在准备用 Go 来感受一下原汁原味的 gRPC 程序开发。
本文的特点是直接用代码说话,通过开箱即用的完整代码,来介绍 gRPC 的各种使用方法。
代码已经上传到 GitHub,下面正式开始。
介绍
gRPC 是 Google 公司基于 Protobuf 开发的跨语言的开源 RPC 框架。gRPC 基于 HTTP/2 协议设计,可以基于一个 HTTP/2 链接提供多个服务,对于移动设备更加友好。
入门
首先来看一个最简单的 gRPC 服务,第一步是定义 proto 文件,因为 gRPC 也是 C/S 架构,这一步相当于明确接口规范。
proto
syntax = "proto3"; package proto; // The greeting service definition. service Greeter { // Sends a greeting rpc SayHello (HelloRequest) returns (HelloReply) {} } // The request message containing the user's name. message HelloRequest { string name = 1; } // The response message containing the greetings message HelloReply { string message = 1; }
使用 protoc-gen-go 内置的 gRPC 插件生成 gRPC 代码:
protoc --go_out=plugins=grpc:. helloworld.proto
执行完这个命令之后,会在当前目录生成一个 helloworld.pb.go 文件,文件中分别定义了服务端和客户端的接口:
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. type GreeterClient interface { // Sends a greeting SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) } // GreeterServer is the server API for Greeter service. type GreeterServer interface { // Sends a greeting SayHello(context.Context, *HelloRequest) (*HelloReply, error) }
接下来就是写服务端和客户端的代码,分别实现对应的接口。
server
package main import ( "context" "fmt" "grpc-server/proto" "log" "net" "google.golang.org/grpc" "google.golang.org/grpc/reflection" ) type greeter struct { } func (*greeter) SayHello(ctx context.Context, req *proto.HelloRequest) (*proto.HelloReply, error) { fmt.Println(req) reply := &proto.HelloReply{Message: "hello"} return reply, nil } func main() { lis, err := net.Listen("tcp", ":50051") if err != nil { log.Fatalf("failed to listen: %v", err) } server := grpc.NewServer() // 注册 grpcurl 所需的 reflection 服务 reflection.Register(server) // 注册业务服务 proto.RegisterGreeterServer(server, &greeter{}) fmt.Println("grpc server start ...") if err := server.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } }
client
package main import ( "context" "fmt" "grpc-client/proto" "log" "google.golang.org/grpc" ) func main() { conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure()) if err != nil { log.Fatal(err) } defer conn.Close() client := proto.NewGreeterClient(conn) reply, err := client.SayHello(context.Background(), &proto.HelloRequest{Name: "zhangsan"}) if err != nil { log.Fatal(err) } fmt.Println(reply.Message) }
这样就完成了最基础的 gRPC 服务的开发,接下来我们就在这个「基础模板」上不断丰富,学习更多特性。
流方式
接下来看看流的方式,顾名思义,数据可以源源不断的发送和接收。
流的话分单向流和双向流,这里我们直接通过双向流来举例。
proto
service Greeter { // Sends a greeting rpc SayHello (HelloRequest) returns (HelloReply) {} // Sends stream message rpc SayHelloStream (stream HelloRequest) returns (stream HelloReply) {} }
增加一个流函数 SayHelloStream
,通过 stream
关键词来指定流特性。
需要重新生成 helloworld.pb.go 文件,这里不再多说。
server
func (*greeter) SayHelloStream(stream proto.Greeter_SayHelloStreamServer) error { for { args, err := stream.Recv() if err != nil { if err == io.EOF { return nil } return err } fmt.Println("Recv: " + args.Name) reply := &proto.HelloReply{Message: "hi " + args.Name} err = stream.Send(reply) if err != nil { return err } } }
在「基础模板」上增加 SayHelloStream
函数,其他都不需要变。
client
client := proto.NewGreeterClient(conn) // 流处理 stream, err := client.SayHelloStream(context.Background()) if err != nil { log.Fatal(err) } // 发送消息 go func() { for { if err := stream.Send(&proto.HelloRequest{Name: "zhangsan"}); err != nil { log.Fatal(err) } time.Sleep(time.Second) } }() // 接收消息 for { reply, err := stream.Recv() if err != nil { if err == io.EOF { break } log.Fatal(err) } fmt.Println(reply.Message) }
通过一个 goroutine 发送消息,主程序的 for
循环接收消息。
执行程序会发现,服务端和客户端都不断有打印输出。
验证器
接下来是验证器,这个需求是很自然会想到的,因为涉及到接口之间的请求,那么对参数进行适当的校验是很有必要的。
在这里我们使用 protoc-gen-govalidators 和 go-grpc-middleware 来实现。
先安装:
go get github.com/mwitkow/go-proto-validators/protoc-gen-govalidators go get github.com/grpc-ecosystem/go-grpc-middleware
接下来修改 proto 文件:
proto
import "github.com/mwitkow/go-proto-validators@v0.3.2/validator.proto"; message HelloRequest { string name = 1 [ (validator.field) = {regex: "^[z]{2,5}$"} ]; }
在这里对 name
参数进行校验,需要符合正则的要求才可以正常请求。
还有其他验证规则,比如对数字大小进行验证等,这里不做过多介绍。
接下来生成 *.pb.go 文件:
protoc \ --proto_path=${GOPATH}/pkg/mod \ --proto_path=${GOPATH}/pkg/mod/github.com/gogo/protobuf@v1.3.2 \ --proto_path=. \ --govalidators_out=. --go_out=plugins=grpc:.\ *.proto
执行成功之后,目录下会多一个 helloworld.validator.pb.go 文件。
这里需要特别注意一下,使用之前的简单命令是不行的,需要使用多个 proto_path
参数指定导入 proto 文件的目录。
官方给了两种依赖情况,一个是 google protobuf,一个是 gogo protobuf。我这里使用的是第二种。
即使使用上面的命令,也有可能会遇到这个报错:
Import "github.com/mwitkow/go-proto-validators/validator.proto" was not found or had errors
但不要慌,大概率是引用路径的问题,一定要看好自己的安装版本,以及在 GOPATH
中的具体路径。
最后是服务端代码改造:
引入包:
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" grpc_validator "github.com/grpc-ecosystem/go-grpc-middleware/validator"
然后在初始化的时候增加验证器功能:
server := grpc.NewServer( grpc.UnaryInterceptor( grpc_middleware.ChainUnaryServer( grpc_validator.UnaryServerInterceptor(), ), ), grpc.StreamInterceptor( grpc_middleware.ChainStreamServer( grpc_validator.StreamServerInterceptor(), ), ), )
启动程序之后,我们再用之前的客户端代码来请求,会收到报错:
2021/10/11 18:32:59 rpc error: code = InvalidArgument desc = invalid field Name: value 'zhangsan' must be a string conforming to regex "^[z]{2,5}$" exit status 1
因为 name: zhangsan
是不符合服务端正则要求的,但是如果传参 name: zzz
,就可以正常返回了。
Token 认证
终于到认证环节了,先看 Token 认证方式,然后再介绍证书认证。
先改造服务端,有了上文验证器的经验,那么可以采用同样的方式,写一个拦截器,然后在初始化 server 时候注入。
认证函数:
func Auth(ctx context.Context) error { md, ok := metadata.FromIncomingContext(ctx) if !ok { return fmt.Errorf("missing credentials") } var user string var password string if val, ok := md["user"]; ok { user = val[0] } if val, ok := md["password"]; ok { password = val[0] } if user != "admin" || password != "admin" { return grpc.Errorf(codes.Unauthenticated, "invalid token") } return nil }
metadata.FromIncomingContext
从上下文读取用户名和密码,然后和实际数据进行比较,判断是否通过认证。
拦截器:
var authInterceptor grpc.UnaryServerInterceptor authInterceptor = func( ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler, ) (resp interface{}, err error) { //拦截普通方法请求,验证 Token err = Auth(ctx) if err != nil { return } // 继续处理请求 return handler(ctx, req) }
初始化:
server := grpc.NewServer( grpc.UnaryInterceptor( grpc_middleware.ChainUnaryServer( authInterceptor, grpc_validator.UnaryServerInterceptor(), ), ), grpc.StreamInterceptor( grpc_middleware.ChainStreamServer( grpc_validator.StreamServerInterceptor(), ), ), )
除了上文的验证器,又多了 Token 认证拦截器 authInterceptor
。
最后是客户端改造,客户端需要实现 PerRPCCredentials
接口。
type PerRPCCredentials interface { // GetRequestMetadata gets the current request metadata, refreshing // tokens if required. This should be called by the transport layer on // each request, and the data should be populated in headers or other // context. If a status code is returned, it will be used as the status // for the RPC. uri is the URI of the entry point for the request. // When supported by the underlying implementation, ctx can be used for // timeout and cancellation. // TODO(zhaoq): Define the set of the qualified keys instead of leaving // it as an arbitrary string. GetRequestMetadata(ctx context.Context, uri ...string) ( map[string]string, error, ) // RequireTransportSecurity indicates whether the credentials requires // transport security. RequireTransportSecurity() bool }
GetRequestMetadata
方法返回认证需要的必要信息,RequireTransportSecurity
方法表示是否启用安全链接,在生产环境中,一般都是启用的,但为了测试方便,暂时这里不启用了。
实现接口:
type Authentication struct { User string Password string } func (a *Authentication) GetRequestMetadata(context.Context, ...string) ( map[string]string, error, ) { return map[string]string{"user": a.User, "password": a.Password}, nil } func (a *Authentication) RequireTransportSecurity() bool { return false }
连接:
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure(), grpc.WithPerRPCCredentials(&auth))
好了,现在我们的服务就有 Token 认证功能了。如果用户名或密码错误,客户端就会收到:
2021/10/11 20:39:35 rpc error: code = Unauthenticated desc = invalid token exit status 1
如果用户名和密码正确,则可以正常返回。
单向证书认证
证书认证分两种方式:
- 单向认证
- 双向认证
先看一下单向认证方式:
生成证书
首先通过 openssl 工具生成自签名的 SSL 证书。
1、生成私钥:
openssl genrsa -des3 -out server.pass.key 2048
2、去除私钥中密码:
openssl rsa -in server.pass.key -out server.key
3、生成 csr 文件:
openssl req -new -key server.key -out server.csr -subj "/C=CN/ST=beijing/L=beijing/O=grpcdev/OU=grpcdev/CN=example.grpcdev.cn"
4、生成证书:
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
再多说一句,分别介绍一下 X.509 证书包含的三个文件:key,csr 和 crt。
- key: 服务器上的私钥文件,用于对发送给客户端数据的加密,以及对从客户端接收到数据的解密。
- csr: 证书签名请求文件,用于提交给证书颁发机构(CA)对证书签名。
- crt: 由证书颁发机构(CA)签名后的证书,或者是开发者自签名的证书,包含证书持有人的信息,持有人的公钥,以及签署者的签名等信息。
gRPC 代码
证书有了之后,剩下的就是改造程序了,首先是服务端代码。
// 证书认证-单向认证 creds, err := credentials.NewServerTLSFromFile("keys/server.crt", "keys/server.key") if err != nil { log.Fatal(err) return } server := grpc.NewServer(grpc.Creds(creds))
只有几行代码需要修改,很简单,接下来是客户端。
由于是单向认证,不需要为客户端单独生成证书,只需要把服务端的 crt 文件拷贝到客户端对应目录下即可。
// 证书认证-单向认证 creds, err := credentials.NewClientTLSFromFile("keys/server.crt", "example.grpcdev.cn") if err != nil { log.Fatal(err) return } conn, err := grpc.Dial("localhost:50051", grpc.WithTransportCredentials(creds))
好了,现在我们的服务就支持单向证书认证了。
但是还没完,这里可能会遇到一个问题:
2021/10/11 21:32:37 rpc error: code = Unavailable desc = connection error: desc = "transport: authentication handshake failed: x509: certificate relies on legacy Common Name field, use SANs or temporarily enable Common Name matching with GODEBUG=x509ignoreCN=0" exit status 1
原因是 Go 1.15 开始废弃了 CommonName,推荐使用 SAN 证书。如果想要兼容之前的方式,可以通过设置环境变量的方式支持,如下:
export GODEBUG="x509ignoreCN=0"
但是需要注意,从 Go 1.17 开始,环境变量就不再生效了,必须通过 SAN 方式才行。所以,为了后续的 Go 版本升级,还是早日支持为好。
双向证书认证
最后来看看双向证书认证。
生成带 SAN 的证书
还是先生成证书,但这次有一点不一样,我们需要生成带 SAN 扩展的证书。
什么是 SAN?
SAN(Subject Alternative Name)是 SSL 标准 x509 中定义的一个扩展。使用了 SAN 字段的 SSL 证书,可以扩展此证书支持的域名,使得一个证书可以支持多个不同域名的解析。
将默认的 OpenSSL 配置文件拷贝到当前目录。
Linux 系统在:
/etc/pki/tls/openssl.cnf
Mac 系统在:
/System/Library/OpenSSL/openssl.cnf
修改临时配置文件,找到 [ req ]
段落,然后将下面语句的注释去掉。
req_extensions = v3_req # The extensions to add to a certificate request
接着添加以下配置:
[ v3_req ] # Extensions to add to a certificate request basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment subjectAltName = @alt_names [ alt_names ] DNS.1 = www.example.grpcdev.cn
[ alt_names ]
位置可以配置多个域名,比如:
[ alt_names ] DNS.1 = www.example.grpcdev.cn DNS.2 = www.test.grpcdev.cn
为了测试方便,这里只配置一个域名。
1、生成 ca 证书:
openssl genrsa -out ca.key 2048 openssl req -x509 -new -nodes -key ca.key -subj "/CN=example.grpcdev.com" -days 5000 -out ca.pem
2、生成服务端证书:
# 生成证书 openssl req -new -nodes \ -subj "/C=CN/ST=Beijing/L=Beijing/O=grpcdev/OU=grpcdev/CN=www.example.grpcdev.cn" \ -config <(cat openssl.cnf \ <(printf "[SAN]\nsubjectAltName=DNS:www.example.grpcdev.cn")) \ -keyout server.key \ -out server.csr # 签名证书 openssl x509 -req -days 365000 \ -in server.csr -CA ca.pem -CAkey ca.key -CAcreateserial \ -extfile <(printf "subjectAltName=DNS:www.example.grpcdev.cn") \ -out server.pem
3、生成客户端证书:
# 生成证书 openssl req -new -nodes \ -subj "/C=CN/ST=Beijing/L=Beijing/O=grpcdev/OU=grpcdev/CN=www.example.grpcdev.cn" \ -config <(cat openssl.cnf \ <(printf "[SAN]\nsubjectAltName=DNS:www.example.grpcdev.cn")) \ -keyout client.key \ -out client.csr # 签名证书 openssl x509 -req -days 365000 \ -in client.csr -CA ca.pem -CAkey ca.key -CAcreateserial \ -extfile <(printf "subjectAltName=DNS:www.example.grpcdev.cn") \ -out client.pem
gRPC 代码
接下来开始修改代码,先看服务端:
// 证书认证-双向认证 // 从证书相关文件中读取和解析信息,得到证书公钥、密钥对 cert, _ := tls.LoadX509KeyPair("cert/server.pem", "cert/server.key") // 创建一个新的、空的 CertPool certPool := x509.NewCertPool() ca, _ := ioutil.ReadFile("cert/ca.pem") // 尝试解析所传入的 PEM 编码的证书。如果解析成功会将其加到 CertPool 中,便于后面的使用 certPool.AppendCertsFromPEM(ca) // 构建基于 TLS 的 TransportCredentials 选项 creds := credentials.NewTLS(&tls.Config{ // 设置证书链,允许包含一个或多个 Certificates: []tls.Certificate{cert}, // 要求必须校验客户端的证书。可以根据实际情况选用以下参数 ClientAuth: tls.RequireAndVerifyClientCert, // 设置根证书的集合,校验方式使用 ClientAuth 中设定的模式 ClientCAs: certPool, })
再看客户端:
// 证书认证-双向认证 // 从证书相关文件中读取和解析信息,得到证书公钥、密钥对 cert, _ := tls.LoadX509KeyPair("cert/client.pem", "cert/client.key") // 创建一个新的、空的 CertPool certPool := x509.NewCertPool() ca, _ := ioutil.ReadFile("cert/ca.pem") // 尝试解析所传入的 PEM 编码的证书。如果解析成功会将其加到 CertPool 中,便于后面的使用 certPool.AppendCertsFromPEM(ca) // 构建基于 TLS 的 TransportCredentials 选项 creds := credentials.NewTLS(&tls.Config{ // 设置证书链,允许包含一个或多个 Certificates: []tls.Certificate{cert}, // 要求必须校验客户端的证书。可以根据实际情况选用以下参数 ServerName: "www.example.grpcdev.cn", RootCAs: certPool, })
大功告成。
Python 客户端
前面已经说了,gRPC 是跨语言的,那么,本文最后我们用 Python 写一个客户端,来请求 Go 服务端。
使用最简单的方式来实现:
proto 文件就使用最开始的「基础模板」的 proto 文件:
syntax = "proto3"; package proto; // The greeting service definition. service Greeter { // Sends a greeting rpc SayHello (HelloRequest) returns (HelloReply) {} // Sends stream message rpc SayHelloStream (stream HelloRequest) returns (stream HelloReply) {} } // The request message containing the user's name. message HelloRequest { string name = 1; } // The response message containing the greetings message HelloReply { string message = 1; }
同样的,也需要通过命令行的方式生成 pb.py 文件:
python3 -m grpc_tools.protoc -I . --python_out=. --grpc_python_out=. ./*.proto
执行成功之后会在目录下生成 helloworld_pb2.py 和 helloworld_pb2_grpc.py 两个文件。
这个过程也可能会报错:
ModuleNotFoundError: No module named 'grpc_tools'
别慌,是缺少包,安装就好:
pip3 install grpcio pip3 install grpcio-tools
最后看一下 Python 客户端代码:
import grpc import helloworld_pb2 import helloworld_pb2_grpc def main(): channel = grpc.insecure_channel("127.0.0.1:50051") stub = helloworld_pb2_grpc.GreeterStub(channel) response = stub.SayHello(helloworld_pb2.HelloRequest(name="zhangsan")) print(response.message) if __name__ == '__main__': main()
这样,就可以通过 Python 客户端请求 Go 启的服务端服务了。
总结
本文通过实战角度出发,直接用代码说话,来说明 gRPC 的一些应用。
内容包括简单的 gRPC 服务,流处理模式,验证器,Token 认证和证书认证。
除此之外,还有其他值得研究的内容,比如超时控制,REST 接口和负载均衡等。以后还会抽时间继续完善剩下这部分内容。
本文中的代码都经过测试验证,可以直接执行,并且已经上传到 GitHub,小伙伴们可以一遍看源码,一遍对照文章内容来学习。
源码地址:
https://github.com/yongxinz/go-example/tree/main/grpc-example
https://github.com/yongxinz/gopher/tree/main/blog
以上就是《Go语言程序开发gRPC服务》的详细内容,更多关于golang的资料请关注golang学习网公众号!

- 上一篇
- gRPC的发布订阅模式及REST接口和超时控制

- 下一篇
- 实用的Go语言开发工具及使用示例
-
- 典雅的鲜花
- 这篇技术文章真及时,太详细了,很棒,码住,关注楼主了!希望楼主能多写Golang相关的文章。
- 2023-05-25 05:17:20
-
- 清秀的口红
- 这篇博文太及时了,好细啊,感谢大佬分享,mark,关注老哥了!希望老哥能多写Golang相关的文章。
- 2023-03-17 02:49:44
-
- 有魅力的飞机
- 这篇技术文章真是及时雨啊,细节满满,太给力了,收藏了,关注老哥了!希望老哥能多写Golang相关的文章。
- 2023-02-27 14:02:53
-
- 仁爱的白开水
- 很有用,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,帮助很大,总算是懂了,感谢作者分享文章内容!
- 2023-02-25 00:46:01
-
- 阔达的路人
- 好细啊,码住,感谢博主的这篇技术贴,我会继续支持!
- 2023-01-26 13:02:03
-
- 爱撒娇的小笼包
- 这篇文章真是及时雨啊,师傅加油!
- 2023-01-13 02:56:41
-
- 敏感的大侠
- 写的不错,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,看完之后很有帮助,总算是懂了,感谢up主分享博文!
- 2023-01-06 01:49:37
-
- 朴实的蜗牛
- 这篇技术贴太及时了,太细致了,太给力了,码住,关注up主了!希望up主能多写Golang相关的文章。
- 2023-01-01 19:25:28
-
- Golang · Go教程 | 19小时前 |
- TigervncDebian多用户共享桌面超简单教程
- 482浏览 收藏
-
- Golang · Go教程 | 1天前 |
- Go语言新手必看!切片vs数组,一次搞定这两个核心知识点
- 472浏览 收藏
-
- Golang · Go教程 | 1天前 |
- Docker在Debian上运行超简单教程(保姆级教学)
- 210浏览 收藏
-
- Golang · Go教程 | 1天前 |
- Debian设置hostname踩坑记录:权限问题大揭秘
- 334浏览 收藏
-
- Golang · Go教程 | 1天前 |
- Debian装SQLServer?这些问题你一定要注意!
- 284浏览 收藏
-
- Golang · Go教程 | 2天前 |
- Debian系统下Jenkins自动化部署脚本教学
- 367浏览 收藏
-
- Golang · Go教程 | 2天前 |
- DebianSwap服务器应用实测,这些场景真的用得上!
- 319浏览 收藏
-
- Golang · Go教程 | 2天前 |
- Debian跑TigerVNC实测!真香警告,快来看看性能咋样~
- 171浏览 收藏
-
- Golang · Go教程 | 2天前 |
- 在Debian上玩转SQLServer备份还原,手把手教你一步步操作
- 498浏览 收藏
-
- Golang · Go教程 | 2天前 |
- DebianOverlay不会玩?手把手教你轻松定制化安装
- 258浏览 收藏
-
- Golang · Go教程 | 2天前 |
- Go语言实战:time.Ticker&time.After用法区别及避坑技巧
- 240浏览 收藏
-
- Golang · Go教程 | 2天前 |
- Debian系统如何快速定位&干掉那些讨厌的僵尸进程
- 317浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 508次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 484次学习
-
- 茅茅虫AIGC检测
- 茅茅虫AIGC检测,湖南茅茅虫科技有限公司倾力打造,运用NLP技术精准识别AI生成文本,提供论文、专著等学术文本的AIGC检测服务。支持多种格式,生成可视化报告,保障您的学术诚信和内容质量。
- 19次使用
-
- 赛林匹克平台(Challympics)
- 探索赛林匹克平台Challympics,一个聚焦人工智能、算力算法、量子计算等前沿技术的赛事聚合平台。连接产学研用,助力科技创新与产业升级。
- 50次使用
-
- 笔格AIPPT
- SEO 笔格AIPPT是135编辑器推出的AI智能PPT制作平台,依托DeepSeek大模型,实现智能大纲生成、一键PPT生成、AI文字优化、图像生成等功能。免费试用,提升PPT制作效率,适用于商务演示、教育培训等多种场景。
- 57次使用
-
- 稿定PPT
- 告别PPT制作难题!稿定PPT提供海量模板、AI智能生成、在线协作,助您轻松制作专业演示文稿。职场办公、教育学习、企业服务全覆盖,降本增效,释放创意!
- 53次使用
-
- Suno苏诺中文版
- 探索Suno苏诺中文版,一款颠覆传统音乐创作的AI平台。无需专业技能,轻松创作个性化音乐。智能词曲生成、风格迁移、海量音效,释放您的音乐灵感!
- 57次使用
-
- 基于微服务框架go-micro开发gRPC应用程序
- 2023-01-01 282浏览
-
- go-micro开发RPC服务以及运行原理介绍
- 2022-12-31 420浏览
-
- Go快速开发一个RESTfulAPI服务
- 2023-01-01 493浏览
-
- Golang gRPC HTTP协议转换示例
- 2022-12-24 213浏览
-
- GoGrpcGateway兼容HTTP协议文档自动生成网关
- 2022-12-28 135浏览