mkdir grpc-go-client
cd grpc-go-client
go mod grpc-go-client
# 下载并安装gin
go get -u github.com/gin-gonic/gin
Main.go
package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
func main(){
r := gin.Default()
// 测试一个get请求
r.GET("/rest/n/:name", func(c *gin.Context) {
name := c.Param("name")
c.JSON(http.StatusOK, gin.H{
"result": fmt.Sprint(name),
})
})
// Run http server
if err := r.Run(":8052"); err != nil {
fmt.Printf("could not run server: %v", err)
}
}
运行http服务
go run main.go
1、创建pb文件夹和protos文件夹
mkdir pb
mkdir protos
2、创建传输图片的proto文件
photo.proto
syntax = "proto3";
package pb;
option go_package = ".;pb";
message PhotoRuquest{
string name = 1;
bytes databytes = 2;
}
message PhotoResponse{
string res = 1;
}
service Photoer {
rpc SendPhoto (PhotoRuquest) returns (PhotoResponse){}
}
3、编译生成
go get google.golang.org/grpc
protoc --proto_path=./protos/ ./protos/*.proto --go_out=plugins=grpc:./pb
4、在启动文件中引入连接grpc服务器
import (
"grpc-go-client/pb"
"google.golang.org/grpc"
)
const port = ":50051" // 服务器端口
conn,err :=grpc.Dial("localhost"+port,grpc.WithInsecure())
if err !=nil{
log.Fatal(err.Error())
}
defer conn.Close()
// conn 调用相关服务的“通道”
// 用于main函数中调用
func sendImg(client pb.PhotoerClient,data []byte,imgName string){
res,err :=client.SendPhoto(context.Background(),&pb.PhotoRuquest{Databytes:data,Name:imgName})
if err !=nil{
log.Fatal(err.Error())
}
fmt.Println(res.Res)
}
}
//main函数中调用
sendImg(pb.NewPhotoerClient(conn),data,imgFile.Filename)
完整main函数代码
package main
import (
"fmt"
"grpc-go-client/pb"
"io/ioutil"
"log"
"net/http"
"os"
"context"
"github.com/gin-gonic/gin"
"google.golang.org/grpc"
)
const port = ":50051"
func main(){
conn,err :=grpc.Dial("localhost"+port,grpc.WithInsecure())
if err !=nil{
log.Fatal(err.Error())
}
defer conn.Close()
r := gin.Default()
// 上传文件
r.POST("/upload", func(c *gin.Context) {
// 单个文件
imgFile, err := c.FormFile("img")
f,_:=imgFile.Open()
data,_ :=ioutil.ReadAll(f)
sendImg(pb.NewPhotoerClient(conn),data,imgFile.Filename)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"message": err.Error(),
})
return
}
log.Println(imgFile.Filename)
c.JSON(http.StatusOK, gin.H{
"message": fmt.Sprintf("'%s' uploaded!", imgFile.Filename),
})
})
// Run http server
if err := r.Run(":8052"); err != nil {
log.Fatalf("could not run server: %v", err)
}
}
// 功能函数
func sendImg(client pb.PhotoerClient,data []byte,imgName string){
res,err :=client.SendPhoto(context.Background(),&pb.PhotoRuquest{Databytes:data,Name:imgName})
if err !=nil{
log.Fatal(err.Error())
}
fmt.Println(res.Res)
}
}
1、通过viper管理项目的日志配置及后续其他的配置
安装viper
go get -u github.com/spf13/viper
安装zap记录日志
// 安装 lumberjack,zap
go get -u github.com/natefinch/lumberjack
go get -u go.uber.org/zap
2、创建日志相关的文件夹和配置文件
# 创建logs文件夹和config配置文件夹
mkdir logs # 存放日志
mkdir config
3、编写日志的配置文件
logconf.json
{
"level": "info",
"filename": "logs/viper_zap_gin.log",
"maxsize": "1",
"max_age": "30",
"max_backups": "5",
"version": "v1.8"
}
4、使用zap日志库
创建logger文件夹,编写zap库的设置logger.go
package logger
import (
// "grpc-go-client/config" // 配置文件
"github.com/gin-gonic/gin"
"github.com/natefinch/lumberjack"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"net"
"net/http"
"net/http/httputil"
"os"
"runtime/debug"
"strings"
"time"
)
var Logger *zap.Logger
type logConfig struct {
Level string `json:"level"`
Filename string `json:"filename"`
MaxSize int `json:"maxsize"`
MaxAge int `json:"max_age"`
MaxBackups int `json:"max_backups"`
Version string `json:"version"`
}
// InitLogger 初始化Logger
func InitLogger(cfg *logConfig) (err error) {
writeSyncer := getLogWriter(cfg.Filename, cfg.MaxSize, cfg.MaxBackups, cfg.MaxAge)
encoder := getEncoder()
// for human operators.
var l = new(zapcore.Level)
err = l.UnmarshalText([]byte(cfg.Level))
if err != nil {
return
}
// 打印到控制台和日志文件
core := zapcore.NewCore(encoder,
zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stderr), writeSyncer),
l)
Logger = zap.New(core, zap.AddCaller())
return
}
func getEncoder() zapcore.Encoder {
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
encoderConfig.TimeKey = "time"
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
encoderConfig.EncodeDuration = zapcore.SecondsDurationEncoder
encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder
return zapcore.NewJSONEncoder(encoderConfig)
}
func getLogWriter(filename string, maxSize, maxBackup, maxAge int) zapcore.WriteSyncer {
lumberJackLogger := &lumberjack.Logger{
Filename: filename,
MaxSize: maxSize,
MaxBackups: maxBackup,
MaxAge: maxAge,
}
return zapcore.AddSync(lumberJackLogger)
}
// GinLogger 接收gin框架默认的日志
func GinLogger(logger *zap.Logger) gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
query := c.Request.URL.RawQuery
c.Next()
cost := time.Since(start)
logger.Info(path,
zap.Int("status", c.Writer.Status()),
zap.String("method", c.Request.Method),
zap.String("path", path),
zap.String("query", query),
zap.String("ip", c.ClientIP()),
zap.String("user-agent", c.Request.UserAgent()),
zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),
zap.Duration("cost", cost),
)
}
}
// GinRecovery recover掉项目可能出现的panic,并使用zap记录相关日志
func GinRecovery(logger *zap.Logger, stack bool) gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
// Check for a broken connection, as it is not really a
// condition that warrants a panic stack trace.
var brokenPipe bool
if ne, ok := err.(*net.OpError); ok {
if se, ok := ne.Err.(*os.SyscallError); ok {
if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
brokenPipe = true
}
}
}
httpRequest, _ := httputil.DumpRequest(c.Request, false)
if brokenPipe {
logger.Error(c.Request.URL.Path,
zap.Any("error", err),
zap.String("request", string(httpRequest)),
)
// If the connection is dead, we can't write a status to it.
c.Error(err.(error)) // nolint: errcheck
c.Abort()
return
}
if stack {
logger.Error("[Recovery from panic]",
zap.Any("error", err),
zap.String("request", string(httpRequest)),
zap.String("stack", string(debug.Stack())),
)
} else {
logger.Error("[Recovery from panic]",
zap.Any("error", err),
zap.String("request", string(httpRequest)),
)
}
c.AbortWithStatus(http.StatusInternalServerError)
}
}()
c.Next()
}
}
main.go文件中启用viper和zap
viper.SetConfigFile("./config/conf.json") //指定json配置文件的路径
// viper.SetConfigFile("./config/conf.yaml") //指定yaml配置文件的路径
err := viper.ReadInConfig() // 读取配置信息
if err != nil { // 配置失败
panic(fmt.Errorf("Fatal error config file: %s \n", err))
}
if err = viper.Unmarshal(logConf); err != nil {
panic(fmt.Errorf("unmarshal conf failed, err:%s \n", err))
}
// 监控配置文件变化
viper.WatchConfig()
// 在gin中使用日志中间件
r.Use(logger.GinLogger(logger.Logger), logger.GinRecovery(logger.Logger, true))
1、创建APP文件夹,用来放置不同的AI应用
mkdir app
# 在app下创建ping文件夹用于测试
mkdir app/ping
2、路由分组
#在ping下创建hander.go和router.go
touch hander.go router.go
3、创建routers文件夹初始化路由
mkdir routers
touch routers/routers.go
routers.go
package routers
import (
"net/http"
"github.com/gin-gonic/gin"
"grpc-go-client/logger"
)
type Option func(*gin.Engine)
var options = []Option{}
func Include(opts ...Option) {
options = append(options, opts...)
}
func cors() gin.HandlerFunc {
return func(c *gin.Context) {
method := c.Request.Method
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token")
c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS")
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")
c.Header("Access-Control-Allow-Credentials", "true")
if method == "OPTIONS" {
c.AbortWithStatus(http.StatusNoContent)
}
c.Next()
}
}
// 初始化 gin的引擎
func RouterInit() *gin.Engine {
r := gin.New()
// 跨域
r.Use(cors())
// 使用zap日志
r.Use(logger.GinLogger(logger.Logger), logger.GinRecovery(logger.Logger, true))
for _, opt := range options {
opt(r)
}
return r
}
调整后的main.go文件内容
package main
import (
"fmt"
"grpc-go-client/app/ping"
"grpc-go-client/logger"
"grpc-go-client/routers"
"log"
"github.com/spf13/viper"
)
var logConf = new(logger.LogConfig)
func main() {
// viper
viper.SetConfigFile("./config/logconf.json") //指定json配置文件的路径
err := viper.ReadInConfig() // 读取配置信息
if err != nil { // 配置失败
panic(fmt.Errorf("Fatal error config file: %s \n", err))
}
if err = viper.Unmarshal(logConf); err != nil {
panic(fmt.Errorf("unmarshal conf failed, err:%s \n", err))
}
// 监控配置文件变化
viper.WatchConfig()
// zap 初始化
if err := logger.InitLogger(logConf); err != nil {
fmt.Printf("init logger failed, err:%v\n", err)
return
}
// 加载多个APP的路由配置
routers.Include(ping.Routers)
// 初始化所有的app的路由
r := routers.RouterInit()
// Run http server
if err := r.Run(":8052"); err != nil {
log.Fatalf("could not run server: %v", err)
}
}
项目代码略
2、创建grpc服务端
python编译
# 在protos文件夹中
python -m grpc_tools.protoc --python_out=../pb --grpc_python_out=../pb -I . ocrPic.proto
golang编译
protoc --proto_path=./ ocrPic.proto --go_out=plugins=grpc:../pb
报错:
AttributeError: module 'google.protobuf.descriptor' has no attribute '_internal_create_key'
解决
pip install --upgrade protobuf