先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Golang全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注go)
img

正文

// todo: add your logic here and delete this line
SavePath := l.svcCtx.Config.UploadFile.SavePath //上传文件的存储路径
utils.CreateDir(SavePath)
for _, fileInfo := range in.File {
//获取文件名称带后缀
fileNameWithSuffix := path.Base(fileInfo.FileName)
//获取文件的后缀(文件类型)
fileType := path.Ext(fileNameWithSuffix)
//生成UUID防止文件被覆盖
uuidName := strings.Replace(uuid.NewV4().String(), “-”, “”, -1)
saveName := uuidName + fileType

saveFullPath := SavePath + saveName
err := ioutil.WriteFile(saveFullPath, fileInfo.FileData, 0644)
if err != nil {
// handle error
}
}
return &ucenter.BaseResp{
Data: “upload success”,
}, nil
}

  1. 文件下载功能代码位置:internal/logic/filestorage/filedownloadlogic.go
    核心代码逻辑:

// 文件下载
func (l *FileDownloadLogic) FileDownload(in *ucenter.FileInfo, stream ucenter.FileStorage_FileDownloadServer) error {
// todo: add your logic here and delete this line
SavePath := l.svcCtx.Config.UploadFile.SavePath //上传文件的存储路径

filePath := SavePath + in.FileUrl
_, err := os.Stat(filePath)
if err != nil || os.IsNotExist(err) {
return errors.New(“文件不存在”)
}
bytes, err := os.ReadFile(filePath)
if err != nil {
return errors.New(“读取文件失败”)
}

response := &ucenter.FileInfo{}
copier.Copy(response, in)
response.FileName = “go-zero.png”
response.FileData = bytes
if err := stream.Send(response); err != nil {
return err
}
return nil
}

2 API服务代码

  1. yaml配置文件:
  • 修改MaxBytes参数是为了调整api服务处理请求体大小限制,如果MaxBytes不调大,上传文件会失败,同时时api服务会返回 413Request Entity Too Large
  • 但是如果文件过大,则有可能导致内存溢出,因此在yaml中修改MaxBytes参数时,MaxBytes又不能设置的过大。

Name: ucenter-api
Host: 0.0.0.0
Port: 8888

Auth:
AccessSecret: 1a3201qa-8b3d-ed0a-05eb-2e9c9b74f6b7
AccessExpire: 86400

#web请求到此api服务的超时时间
Timeout: 10000

将请求体最大允许字节数从1MB改为1000MB

MaxBytes: 1048576000

#文件
UploadFile:
MaxFileNum: 1000
MaxFileSize: 1048576000 # 1000MB
SavePath: projects/go-zero-micro/uploadFiles/

UCenter 服务

UCenterRpc:
Etcd:
Hosts:

  • 127.0.0.1:2379
    Key: ucenter.rpc
    #api请求rpc服务的超时时间
    Timeout: 10000

#日志配置
Log:
Mode: file
Path: log/go-zero-micro/ucenterapi
Level: error
Compress: true
KeepDays: 180

  1. file.api:注意:这里的文件相关接口上没有加鉴权。

syntax = “v1”

info(
title : “go-zero-micro”
desc: “userapi”
author: “ximuqi”
email: “xxx”
version: “0.0.1”
)

type (
/* 1 上传文件 */
FileUploadReq {
Id int64 form:"id" // 父级-id
Type int64 form:"type,optional" // 类型 1:类型1;2:类型2
FileList []*byte form:"fileList,optional" // 文件列表
}

/* 2 下载/预览文件 */
FileShowReq {
Id int64 form:"id" // 文件-id
FileUrl string form:"fileUrl,optional" // 文件地址
}
)
@server(
group: fileStorage
prefix: /fileStorage
)
service ucenter-api {
@doc(
summary: “1 上传文件”
)
@handler fileUpload
post /fileUpload (FileUploadReq) returns (BaseModel)

@doc(
summary: “2 文件下载”
)
@handler fileDownload
get /fileDownload (FileShowReq)

@doc(
summary: “3 文件预览”
)
@handler filePreview
get /filePreview (FileShowReq)
}

  1. 根据file.api生成go-zero的api服务代码。

goctl api go -api ./doc/all.api -dir ./code/ucenterapi

  1. internal/svc/servicecontext.go加入rpc服务的文件存储相关服务接口

package svc

import (
“github.com/zeromicro/go-zero/rest”
“github.com/zeromicro/go-zero/zrpc”
“go-zero-micro/api/code/ucenterapi/internal/config”
“go-zero-micro/api/code/ucenterapi/internal/middleware”
“go-zero-micro/rpc/code/ucenter/client/filestorage”
“go-zero-micro/rpc/code/ucenter/client/ucentergorm”
“go-zero-micro/rpc/code/ucenter/client/ucentersqlx”
)

type ServiceContext struct {
Config config.Config
Check rest.Middleware
UcenterGormRpc ucentergorm.UcenterGorm //gorm方式的接口
UcenterSqlxRpc ucentersqlx.UcenterSqlx //sqlx方式的接口
FileStorageRpc filestorage.FileStorage //文件存储相关接口
}

func NewServiceContext(c config.Config) *ServiceContext {
uCenterRpcClient := zrpc.MustNewClient(c.UCenterRpc)

return &ServiceContext{
Config: c,
Check: middleware.NewCheckMiddleware().Handle,
UcenterGormRpc: ucentergorm.NewUcenterGorm(uCenterRpcClient),
UcenterSqlxRpc: ucentersqlx.NewUcenterSqlx(uCenterRpcClient),
FileStorageRpc: filestorage.NewFileStorage(uCenterRpcClient),
}
}

  1. 文件上传功能代码位置:internal/logic/fileStorage/fileuploadlogic.go

package fileStorage

import (
“bytes”
“context”
“fmt”
“github.com/jinzhu/copier”
uuid “github.com/satori/go.uuid”
“go-zero-micro/common/utils”
“go-zero-micro/rpc/code/ucenter/ucenter”
“io”
“io/ioutil”
“net/http”
“os”
“path”
“strings”

“go-zero-micro/api/code/ucenterapi/internal/svc”
“go-zero-micro/api/code/ucenterapi/internal/types”

“github.com/zeromicro/go-zero/core/logx”
)

type FileUploadLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}

func NewFileUploadLogic(ctx context.Context, svcCtx *svc.ServiceContext) *FileUploadLogic {
return &FileUploadLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}

func (l *FileUploadLogic) FileUpload(request *http.Request, req *types.FileUploadReq) (resp *types.BaseModel, err error) {
// todo: add your logic here and delete this line
//return LocalFileToByte(l, request, requestBody)
return FileToByte(l, request, req)
}

// LocalFileToByte 方式1:ioutil.ReadFile()转换成byte需要知道文件路径,因此会生成临时文件,不适合处理文件上传的场景
func LocalFileToByte(l *FileUploadLogic, request *http.Request, requestBody *types.FileUploadReq) (resp *types.BaseModel, err error) {
SavePath := l.svcCtx.Config.UploadFile.SavePath //上传文件的存储路径
utils.CreateDir(SavePath)
files := request.MultipartForm.File[“fileList”]
res := &types.BaseModel{
Data: “上传成功”,
}
param := &ucenter.FileList{}
copier.Copy(param, requestBody)
rpcFileList := make([]*ucenter.FileInfo, 0)
typeId := fmt.Sprintf(“%d”, requestBody.Type)
// 遍历所有文件
for _, fileHeader := range files {
//获取文件大小
fileSize := fileHeader.Size
//获取文件名称带后缀
fileNameWithSuffix := path.Base(fileHeader.Filename)
//获取文件的后缀(文件类型)
fileType := path.Ext(fileNameWithSuffix)
//生成UUID防止文件被覆盖
uuidName := typeId + “_” + strings.Replace(uuid.NewV4().String(), “-”, “”, -1)

saveName := uuidName + fileType
saveFullPath := SavePath + saveName
logx.Infof(“upload file: %+v, file size: %d”, fileNameWithSuffix, fileSize)
file, err := fileHeader.Open()
tempFile, err := os.Create(saveFullPath)
if err != nil {
return nil, err
}
io.Copy(tempFile, file)
//关闭文件
file.Close()
tempFile.Close()

//方式1:ioutil.ReadFile()转换成byte需要知道文件路径,不适合处理文件上传的场景
content, err := ioutil.ReadFile(saveFullPath)
fileInfo := &ucenter.FileInfo{
FileId: requestBody.Id,
FileName: fileNameWithSuffix,
FileType: fileType,
FileSize: fileSize,
FileData: content,
}
err = os.Remove(saveFullPath)
if err != nil {
logx.Infof(“%s:删除失败”, fileNameWithSuffix)
}
rpcFileList = append(rpcFileList, fileInfo)
}
param.File = rpcFileList
uploadRes, err := l.svcCtx.FileStorageRpc.FileUpload(l.ctx, param)
if err != nil {
return nil, err
}
res.Data = uploadRes.Data
return res, nil
}

// FileToByte 方式2:转换成byte适合上传文件的场景
func FileToByte(l *FileUploadLogic, request *http.Request, requestBody *types.FileUploadReq) (resp *types.BaseModel, err error) {
files := request.MultipartForm.File[“fileList”]
res := &types.BaseModel{
Data: “上传成功”,
}
param := &ucenter.FileList{}
copier.Copy(param, requestBody)
rpcFileList := make([]*ucenter.FileInfo, 0)
// 遍历所有文件
for _, fileHeader := range files {
//获取文件大小
fileSize := fileHeader.Size
//获取文件名称带后缀
fileNameWithSuffix := path.Base(fileHeader.Filename)
//获取文件的后缀(文件类型)
fileType := path.Ext(fileNameWithSuffix)
logx.Infof(“upload file: %+v, file size: %d”, fileNameWithSuffix, fileSize)
file, err := fileHeader.Open()
if err != nil {
return nil, err
}
//方式2:转换成byte适合上传文件的场景
fil := make([][]byte, 0)
var b int64 = 0
// 通过for循环写入
for {
buffer := make([]byte, 1024)
n, err := file.ReadAt(buffer, b)
b = b + int64(n)
fil = append(fil, buffer)
if err != nil {
fmt.Println(err.Error())
break
}
}
// 生成最后的文件字节流
content := bytes.Join(fil, []byte(“”))
fileInfo := &ucenter.FileInfo{
FileId: requestBody.Id,
FileName: fileNameWithSuffix,
FileType: fileType,
FileSize: fileSize,
FileData: content,
}
rpcFileList = append(rpcFileList, fileInfo)
}
param.File = rpcFileList
uploadRes, err := l.svcCtx.FileStorageRpc.FileUpload(l.ctx, param)
if err != nil {
return nil, err
}
res.Data = uploadRes.Data
return res, nil
}

  1. 文件下载功能代码位置:internal/logic/fileStorage/filedownloadlogic.go,下载是写入到响应流中的,所以需要加入 writer http.ResponseWriter

package fileStorage

import (
“context”
“errors”
“github.com/jinzhu/copier”
“go-zero-micro/api/code/ucenterapi/internal/svc”
“go-zero-micro/api/code/ucenterapi/internal/types”
“go-zero-micro/rpc/code/ucenter/ucenter”
“net/http”

“github.com/zeromicro/go-zero/core/logx”
)

type FileDownloadLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
writer http.ResponseWriter
}

func NewFileDownloadLogic(ctx context.Context, svcCtx *svc.ServiceContext, writer http.ResponseWriter) *FileDownloadLogic {
return &FileDownloadLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
writer: writer,
}
}

func (l *FileDownloadLogic) FileDownload(req *types.FileShowReq) error {
// todo: add your logic here and delete this line
param := &ucenter.FileInfo{}
copier.Copy(param, req)
downloadRes, err := l.svcCtx.FileStorageRpc.FileDownload(l.ctx, param)
if err != nil {
return errors.New(“文件服务异常”)
}
fileInfo, err := downloadRes.Recv()
if err != nil {
return errors.New(“文件下载失败”)
}
fileName := fileInfo.FileName
byteArr := fileInfo.FileData

//如果是下载,则需要在Header中设置这两个参数
//l.writer.Header().Add(“Content-Type”, “application/octet-stream”)
//l.writer.Header().Add(“Content-Disposition”, "attachment; filename= "+fileName)

l.writer.Header().Add(“Content-Type”, “application/octet-stream”)
l.writer.Header().Add(“Content-Disposition”, "attachment; filename= "+fileName)
l.writer.Write(byteArr)
return nil
}

  1. 文件预览功能代码位置:internal/logic/fileStorage/filepreviewlogic.go(预览只针对浏览器支持的类型有效),预览和下载一样是写入到响应流中的,所以也需要加入 writer http.ResponseWriter

package fileStorage

import (
“context”
“errors”
“github.com/jinzhu/copier”
“go-zero-micro/rpc/code/ucenter/ucenter”
“net/http”

“go-zero-micro/api/code/ucenterapi/internal/svc”
“go-zero-micro/api/code/ucenterapi/internal/types”

“github.com/zeromicro/go-zero/core/logx”
)

type FilePreviewLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
writer http.ResponseWriter
}

func NewFilePreviewLogic(ctx context.Context, svcCtx *svc.ServiceContext, writer http.ResponseWriter) *FilePreviewLogic {
return &FilePreviewLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
writer: writer,
}
}

func (l *FilePreviewLogic) FilePreview(req *types.FileShowReq) error {
// todo: add your logic here and delete this line
param := &ucenter.FileInfo{}
copier.Copy(param, req)
downloadRes, err := l.svcCtx.FileStorageRpc.FileDownload(l.ctx, param)
if err != nil {
return errors.New(“文件服务异常”)
}
fileInfo, err := downloadRes.Recv()
if err != nil {
return errors.New(“打开文件失败”)
}
//fileName := fileInfo.FileName
byteArr := fileInfo.FileData

//如果是下载,则需要在Header中设置这两个参数
//l.writer.Header().Add(“Content-Type”, “application/octet-stream”)
//l.writer.Header().Add(“Content-Disposition”, "attachment; filename= "+fileName)
l.writer.Write(byteArr)
return nil
}

3 功能测试

  1. 文件上传功能

请求地址:http://localhost:8888/fileStorage/fileUpload
请求方式:POST
请求格式:FORM
请求数据:id=1,type=1,fileList(文件类型)
返回结果:

{
“code”: 200,
“msg”: “success”,
“success”: true,
“data”: {
“id”: 0,
“name”: “”,
“data”: “upload success”
}
}

  1. 文件下载功能

请求地址:http://127.0.0.1:8888/fileStorage/fileDownload?id=1&fileUrl=d3d22404926349fd8cb1924b12cadae8.png
请求方式:GET
请求格式:URL
请求数据:id=1&fileUrl=d3d22404926349fd8cb1924b12cadae8.png
返回结果:请求成功后会立即下载。

  1. 文件预览功能

请求地址:http://127.0.0.1:8888/fileStorage/filePreview?id=1&fileUrl=d3d22404926349fd8cb1924b12cadae8.png
请求方式:GET
请求格式:URL
请求数据:id=1&fileUrl=d3d22404926349fd8cb1924b12cadae8.png
返回结果:请求成功后会立即显示在浏览器页面上。

4 注意事项

  1. api服务调用rpc服务报错并返回错误信息:unknown service ucenter.fileStorage
    原因:在启动类中rpc里新加的文件存储相关服务没有注册到rpc服务中,如果启动类的所有代码一直是使用goctl的命令自动生成的,则可能不会出现这个问题。
    解决办法:手动在启动类里加上注册文件存储相关服务的代码。

package main

import (
“flag”
“fmt”
filestorageServer “go-zero-micro/rpc/code/ucenter/internal/server/filestorage”

“go-zero-micro/rpc/code/ucenter/internal/config”
ucentergormServer “go-zero-micro/rpc/code/ucenter/internal/server/ucentergorm”
ucentersqlxServer “go-zero-micro/rpc/code/ucenter/internal/server/ucentersqlx”
“go-zero-micro/rpc/code/ucenter/internal/svc”
“go-zero-micro/rpc/code/ucenter/ucenter”

“github.com/zeromicro/go-zero/core/conf”
“github.com/zeromicro/go-zero/core/service”
“github.com/zeromicro/go-zero/zrpc”
“google.golang.org/grpc”
“google.golang.org/grpc/reflection”
)

var configFile = flag.String(“f”, “conf/dev/rpc/ucenter.yaml”, “the config file”)

func main() {
flag.Parse()

var c config.Config
conf.MustLoad(*configFile, &c)
ctx := svc.NewServiceContext©

s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
ucenter.RegisterUcenterSqlxServer(grpcServer, ucentersqlxServer.NewUcenterSqlxServer(ctx))
ucenter.RegisterUcenterGormServer(grpcServer, ucentergormServer.NewUcenterGormServer(ctx))

//新增的分组接口必须要在这里注册,根据proto生成时可能未新增,否则会报 unknown service ucenter.fileStorage
ucenter.RegisterFileStorageServer(grpcServer, filestorageServer.NewFileStorageServer(ctx))

if c.Mode == service.DevMode || c.Mode == service.TestMode {
reflection.Register(grpcServer)
}
})
defer s.Stop()

fmt.Printf(“Starting rpc server at %s…\n”, c.ListenOn)
s.Start()
}

  1. 文件上传请求超时
    如果文件过大,在上传文件时可能会出现请求等待一段时间后没有返回响应信息。
    原因
    (1)超时时间设置过短;
    (2)api往rpc发送信息时,数据过大;

解决办法
2.5 go-zero超时时间2.6节 grpc请求体大小限制中讲解。

2.5 go-zero超时时间

本次示例代码

在处理请求时有可能出现时间过长的情况,go-zero设置了默认的超时时间(单位是毫秒)避免等待过长。

在go-zero微服务超时时间的设置有三处:

  1. rpc服务处理grpc请求时(grpc服务端);
  2. api服务调用rpc服务时(grpc客户端);
  3. api服务处理http请求时;

其中1是在rpc服务中配置,2、3是在api服务中配置。如果是单体服务,则只需要配置3. api服务处理http请求时即可。

以登录超时演示实际效果:先配置rpc服务中超时,再配置api服务中的超时。

1 rpc服务处理grpc请求时(grpc服务端):

这里是在rpc服务的配置文件yaml中设置,注意Timeout的层级位置。

Name: ucenter.rpc
ListenOn: 0.0.0.0:8080
Etcd:
Hosts:

  • 127.0.0.1:2379
    Key: ucenter.rpc

#rpc处理超时时间 10秒
Timeout: 10000

#开启grpc调试模式
Mode: dev

JWT:
AccessSecret: 1a3201qa-8b3d-ed0a-05eb-2e9c9b74f6b7
AccessExpire: 86400

#文件
UploadFile:
MaxFileNum: 1000
MaxFileSize: 1048576000 # 1000MB
SavePath: projects/go-zero-micro/uploadFiles/

#日志配置
Log:
Mode: file
Path: log/go-zero-micro/ucenterpc
Level: error
Compress: true
KeepDays: 180

zrpc.RpcClientConf 中可以查看到go-zero设置的默认超时时间是2000毫秒

// A RpcServerConf is a rpc server config.
RpcServerConf struct {
service.ServiceConf
ListenOn string
Etcd discov.EtcdConf json:",optional,inherit"
Auth bool json:",optional"
Redis redis.RedisKeyConf json:",optional"
StrictControl bool json:",optional"
// setting 0 means no timeout
Timeout int64 json:",default=2000"
CpuThreshold int64 json:",default=900,range=[0:1000]"
// grpc health check switch
Health bool json:",default=true"
Middlewares ServerMiddlewaresConf
}

rpc服务模拟登录逻辑处理超时,在internal/logic/ucentersqlx/loginuserlogic.go中设置睡眠10秒钟。

package ucentersqlxlogic

import (
“context”
“go-zero-micro/common/utils”
“time”

“go-zero-micro/rpc/code/ucenter/internal/svc”
“go-zero-micro/rpc/code/ucenter/ucenter”

“github.com/jinzhu/copier”
“github.com/zeromicro/go-zero/core/logx”
)

type LoginUserLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}

func NewLoginUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LoginUserLogic {
return &LoginUserLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
}
}

// LoginUser 用户登录
func (l *LoginUserLogic) LoginUser(in *ucenter.User) (*ucenter.UserLoginResp, error) {
// todo: add your logic here and delete this line
//return &ucenter.UserLoginResp{}, nil

//模拟耗时 20秒钟
sleepTime := 20 * time.Second
time.Sleep(sleepTime)
return l.LoginSuccess(in)
}

func (l *LoginUserLogic) LoginSuccess(in *ucenter.User) (*ucenter.UserLoginResp, error) {
AccessSecret := l.svcCtx.Config.JWT.AccessSecret
AccessExpire := l.svcCtx.Config.JWT.AccessExpire
now := time.Now().Unix()

jwtToken, err := utils.GenerateJwtToken(AccessSecret, now, AccessExpire, in.Id)
if err != nil {
return nil, err
}
resp := &ucenter.UserLoginResp{}
copier.Copy(resp, in)
resp.AccessToken = jwtToken
resp.AccessExpire = now + AccessExpire
resp.RefreshAfter = now + AccessExpire/2
return resp, nil
}

rpc登录超时测试
因已在配置文件中开启了grpc调试模式,故可以直接使用postman进行grpc请求测试。

请求地址:http://127.0.0.1:8080
请求方式:GRPC
请求格式:JSON
请求数据:{“account”:“hello”,“password”:“123456”}
测试结果:Status code: 4 DEADLINE_EXCEEDED Time: 9982 ms

结论:配置rpc服务处理grpc请求超时为10秒钟,模拟登录处理耗时为20秒钟,超过rpc服务配置的超时时间,故rpc在10秒钟后返回结果,所以rpc配置的超时时间已生效。


2 api服务调用rpc服务时(grpc客户端):

是在api服务的配置文件yaml中设置,它是设置在UCenterRpc下,这里设置10秒钟,注意Timeout的层级位置。

Name: ucenter-api
Host: 0.0.0.0
Port: 8888

Auth:
AccessSecret: 1a3201qa-8b3d-ed0a-05eb-2e9c9b74f6b7
AccessExpire: 86400

#web请求到此api服务的超时时间
Timeout: 10000

将请求体最大允许字节数从1MB改为1000MB

MaxBytes: 1048576000

#文件
UploadFile:
MaxFileNum: 1000
MaxFileSize: 1048576000 # 1000MB
SavePath: projects/go-zero-micro/uploadFiles/

UCenter 服务

UCenterRpc:
Etcd:
Hosts:

  • 127.0.0.1:2379
    Key: ucenter.rpc
    #api请求rpc服务的超时时间
    Timeout: 5000

#日志配置
Log:
Mode: file
Path: log/go-zero-micro/ucenterapi
Level: error
Compress: true
KeepDays: 180

zrpc.RpcClientConf 中可以查看到go-zero设置的默认超时时间是2000毫秒

// A RpcClientConf is a rpc client config.
RpcClientConf struct {
Etcd discov.EtcdConf json:",optional,inherit"
Endpoints []string json:",optional"
Target string json:",optional"
App string json:",optional"
Token string json:",optional"
NonBlock bool json:",optional"
Timeout int64 json:",default=2000"
KeepaliveTime time.Duration json:",optional"
Middlewares ClientMiddlewaresConf
}

3 api服务处理http请求时:2 api服务调用rpc服务时(grpc客户端)一样是在api服务的配置文件yaml中设置,注意Timeout的层级位置。

rest.RestConf 中可以查看到go-zero设置的默认超时时间是3000毫秒

RestConf struct {
service.ServiceConf
Host string json:",default=0.0.0.0"
Port int
CertFile string json:",optional"
KeyFile string json:",optional"
Verbose bool json:",optional"
MaxConns int json:",default=10000"
MaxBytes int64 json:",default=1048576"
// milliseconds
Timeout int64 json:",default=3000"
CpuThreshold int64 json:",default=900,range=[0:1000]"
Signature SignatureConf json:",optional"
// There are default values for all the items in Middlewares.
Middlewares MiddlewaresConf
// TraceIgnorePaths is paths blacklist for trace middleware.
TraceIgnorePaths []string json:",optional"
}

记录api调用rpc服务的超时时间,在 internal/logic/login/loginbypasswordlogic.go

package login

import (
“context”
“fmt”
“github.com/jinzhu/copier”
“go-zero-micro/common/errorx”
“go-zero-micro/rpc/code/ucenter/ucenter”
“time”

“go-zero-micro/api/code/ucenterapi/internal/svc”
“go-zero-micro/api/code/ucenterapi/internal/types”

“github.com/zeromicro/go-zero/core/logx”
)

type LoginByPasswordLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}

func NewLoginByPasswordLogic(ctx context.Context, svcCtx *svc.ServiceContext) *LoginByPasswordLogic {
return &LoginByPasswordLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}

func (l *LoginByPasswordLogic) LoginByPassword(req *types.UserLoginPasswordModel) (resp *types.UserLoginResp, err error) {
// todo: add your logic here and delete this line
param := &ucenter.User{}
copier.Copy(param, req)

fmt.Printf(“call rpc start\n”)
startTime := time.Now()
loginRes, err := l.svcCtx.UcenterSqlxRpc.LoginUser(l.ctx, param)

cost := time.Since(startTime) / time.Second
fmt.Printf(“call rpc end:%d秒\n”, cost)
if err != nil {
return nil, errorx.NewDefaultError(errorx.UserLoginPasswordErrorCode)
}
res := &types.UserLoginResp{}
copier.Copy(res, loginRes)
return res, nil
}

4 模拟登录测试

第一次测试
请求地址:http://localhost:8888/login/loginByPassword
请求方式:POST
请求格式:JSON
请求数据:{“account”:“hello”,“password”:“123456”}
测试结果:
(1)api调用rpc耗时5秒钟;
(2)api服务处理http请求耗时5秒钟;

结论:因api服务调用rpc服务时超时时间为5秒钟,rpc在处理登录逻辑时,模拟登录处理耗时为20秒钟,超过api调用rpc配置的超时时间,故api在5秒钟后返回结果,所以 2 api服务调用rpc服务时(grpc客户端)配置的超时时间已生效


第二次测试:(这次把api服务处理http请求时的超时时间由10秒减为2秒)

请求地址:http://localhost:8888/login/loginByPassword
请求方式:POST
请求格式:JSON
请求数据:{“account”:“hello”,“password”:“123456”}
测试结果:
(1)api服务处理http请求耗时2秒钟;

结论:因api服务处理http请求时的超时时间为2秒钟,所以当api处理登录逻辑超过2秒钟后。直接返回登陆超时,所以 3 api服务处理http请求时配置的超时时间已生效

综上:在api服务调用rpc服务时,超时时间以最小者为优先生效。


2.6 grpc服务端接收请求体大小限制

本次示例代码

API服务往RPC服务传递数据实际就是grpc的客户端grpc的服务端发送数据。

grpc的服务端在接收数据时默认是有数据大小限制的,本次在2.5超时时间基础上进一步学习了解。

1 调整配置:

  1. 调整超时时间:将rpc和api的yaml中将超时时间统一调整为600000毫秒(即10分钟)
  2. rpc的yaml中关闭grpc调试模式
  3. rpc的登录处理逻辑屏蔽模拟耗时操作
  4. api的yaml中将MaxBytes从1MB改为10000MB

2 大文件上传测试

请求地址:http://localhost:8888/fileStorage/fileUpload
请求方式:POST
请求格式:FORM
请求数据:id=1,type=1,fileList(文件类型,文件大小:167,840,304 字节,约160 MB
测试结果:在 internal/logic/fileStorage/fileuploadlogic.go调用rpc服务的地方查看返回的错误信息,或者将rpc返回的错误信息返回给前端。
在这里插入图片描述
可以看到api往rpc传输文件失败的原因是文件的大小167840818字节(约160 MB)超过了rpc端(grpc服务端)消息体接收限制4194304字节(约4MB)的大小。

3 调整grpc服务端接收请求体大小限制
在启动类里加上:

MaxFileSize := int(c.UploadFile.MaxFileSize)
//调整RPC服务端收到的消息体大小限制
s.AddOptions(grpc.MaxRecvMsgSize(MaxFileSize))

完整代码:

package main

import (
“flag”
“fmt”
filestorageServer “go-zero-micro/rpc/code/ucenter/internal/server/filestorage”

“go-zero-micro/rpc/code/ucenter/internal/config”
ucentergormServer “go-zero-micro/rpc/code/ucenter/internal/server/ucentergorm”
ucentersqlxServer “go-zero-micro/rpc/code/ucenter/internal/server/ucentersqlx”
“go-zero-micro/rpc/code/ucenter/internal/svc”
“go-zero-micro/rpc/code/ucenter/ucenter”

“github.com/zeromicro/go-zero/core/conf”
“github.com/zeromicro/go-zero/core/service”
“github.com/zeromicro/go-zero/zrpc”
“google.golang.org/grpc”
“google.golang.org/grpc/reflection”
)

var configFile = flag.String(“f”, “conf/dev/rpc/ucenter.yaml”, “the config file”)

func main() {
flag.Parse()

var c config.Config
conf.MustLoad(*configFile, &c)
ctx := svc.NewServiceContext©

s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
ucenter.RegisterUcenterSqlxServer(grpcServer, ucentersqlxServer.NewUcenterSqlxServer(ctx))
ucenter.RegisterUcenterGormServer(grpcServer, ucentergormServer.NewUcenterGormServer(ctx))

//新增的分组接口必须要在这里注册,根据proto生成时可能未新增,否则会报 unknown service ucenter.fileStorage
ucenter.RegisterFileStorageServer(grpcServer, filestorageServer.NewFileStorageServer(ctx))

if c.Mode == service.DevMode || c.Mode == service.TestMode {
reflection.Register(grpcServer)
}
})

MaxFileSize := int(c.UploadFile.MaxFileSize)
//调整RPC服务端收到的消息体大小限制
s.AddOptions(grpc.MaxRecvMsgSize(MaxFileSize))

defer s.Stop()

fmt.Printf(“Starting rpc server at %s…\n”, c.ListenOn)
s.Start()
}

2.7 grpc客户端接收响应体大小限制

本次示例代码

2.6 grpc服务端接收请求体大小限制类似,grpc的客户端在接收grpc服务端返回的数据时默认也是有数据大小限制的,限制4194304字节(约4MB),本次在2.6 grpc服务端接收请求体大小限制基础上进一步学习了解。

1. 文件下载功能

请求地址:http://127.0.0.1:8888/fileStorage/fileDownload?id=1&fileUrl=ee2a0940a3b64c81b2e8b125881523df.exe
请求方式:GET
请求格式:URL
请求数据:id=1&fileUrl=ee2a0940a3b64c81b2e8b125881523df.exe
返回结果:
在这里插入图片描述

可以看到api接收rpc传输文件失败的原因是文件的大小167840824字节(约160 MB)超过了rpc端(grpc服务端)消息体接收限制4194304字节(约4MB)的大小。

2 调整grpc客户端接收响应体大小限制

internal/svc/servicecontext.go加上:

MaxFileSize := int(c.UploadFile.MaxFileSize)
//调整RPC客户端收到的消息体大小限制
dialOption := grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(MaxFileSize))
opt := zrpc.WithDialOption(dialOption)

完整代码:

package svc

import (
“github.com/zeromicro/go-zero/rest”
“github.com/zeromicro/go-zero/zrpc”
“go-zero-micro/api/code/ucenterapi/internal/config”
“go-zero-micro/api/code/ucenterapi/internal/middleware”
“go-zero-micro/rpc/code/ucenter/client/filestorage”
“go-zero-micro/rpc/code/ucenter/client/ucentergorm”
“go-zero-micro/rpc/code/ucenter/client/ucentersqlx”
“google.golang.org/grpc”
)

type ServiceContext struct {
Config config.Config
Check rest.Middleware
UcenterGormRpc ucentergorm.UcenterGorm //gorm方式的接口
UcenterSqlxRpc ucentersqlx.UcenterSqlx //sqlx方式的接口
FileStorageRpc filestorage.FileStorage //文件存储相关接口
}

func NewServiceContext(c config.Config) *ServiceContext {
MaxFileSize := int(c.UploadFile.MaxFileSize)
//调整RPC客户端收到的消息体大小限制
dialOption := grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(MaxFileSize))
opt := zrpc.WithDialOption(dialOption)

uCenterRpcClient := zrpc.MustNewClient(c.UCenterRpc, opt)

return &ServiceContext{
Config: c,
Check: middleware.NewCheckMiddleware().Handle,
UcenterGormRpc: ucentergorm.NewUcenterGorm(uCenterRpcClient),
UcenterSqlxRpc: ucentersqlx.NewUcenterSqlx(uCenterRpcClient),
FileStorageRpc: filestorage.NewFileStorage(uCenterRpcClient),
}
}


2.8 API和RPC服务拦截器

本次示例代码

API、RPC服务拦截器又称RPC的客户端、服务端拦截器。

根据功能不同可以声明创建不同的拦截器,然后加入到拦截器链上,多个拦截器的拦截顺序是先进后出,即先触发的拦截器,最后才处理结束。

注意:这里指的拦截器指的是微服务之间调用时生效,即API服务是在发往RPC数据时拦截,RPC服务是在接收API服务时拦截。

1 API服务拦截器
API服务拦截器主要是在 internal/svc/servicecontext.go中声明和使用,查看zrpc.WithUnaryClientInterceptor()源码可以发现,底层还是调用的grpc的拦截器。

初次使用:

package svc

import (
“context”
“fmt”
“github.com/zeromicro/go-zero/rest”
“github.com/zeromicro/go-zero/zrpc”
“go-zero-micro/api/code/ucenterapi/internal/config”
“go-zero-micro/api/code/ucenterapi/internal/middleware”
“go-zero-micro/rpc/code/ucenter/client/filestorage”
“go-zero-micro/rpc/code/ucenter/client/ucentergorm”
“go-zero-micro/rpc/code/ucenter/client/ucentersqlx”
“google.golang.org/grpc”
)

type ServiceContext struct {
Config config.Config
Check rest.Middleware
UcenterGormRpc ucentergorm.UcenterGorm //gorm方式的接口
UcenterSqlxRpc ucentersqlx.UcenterSqlx //sqlx方式的接口
FileStorageRpc filestorage.FileStorage //文件存储相关接口
}

func NewServiceContext(c config.Config) *ServiceContext {
MaxFileSize := int(c.UploadFile.MaxFileSize)
//调整RPC客户端收到的消息体大小限制
dialOption := grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(MaxFileSize))
opt := zrpc.WithDialOption(dialOption)

//声明拦截器
interceptor1 := zrpc.WithUnaryClientInterceptor(func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts …grpc.CallOption) error {
fmt.Printf(“interceptor1 ====> Start \n”)
fmt.Printf(“req =====================> %+v \n”, req)

err := invoker(ctx, method, req, reply, cc, opts…)
fmt.Printf(“interceptor1 ====> End \n”)
if err != nil {
return err
}
return nil
})

//声明拦截器
interceptor2 := zrpc.WithUnaryClientInterceptor(func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts …grpc.CallOption) error {
fmt.Printf(“interceptor2 ====> Start \n”)
fmt.Printf(“req =====================> %+v \n”, req)

err := invoker(ctx, method, req, reply, cc, opts…)
fmt.Printf(“interceptor2 ====> End \n”)
if err != nil {
return err
}
return nil
})

uCenterRpcClient := zrpc.MustNewClient(c.UCenterRpc, opt, interceptor1, interceptor2)

return &ServiceContext{
Config: c,
Check: middleware.NewCheckMiddleware().Handle,
UcenterGormRpc: ucentergorm.NewUcenterGorm(uCenterRpcClient),
UcenterSqlxRpc: ucentersqlx.NewUcenterSqlx(uCenterRpcClient),
FileStorageRpc: filestorage.NewFileStorage(uCenterRpcClient),
}
}

2 RPC服务拦截器

RPC服务的拦截器是在启动类里使用s.AddUnaryInterceptors(),里面的参数是具体的拦截方法,需要注意的是可以重复声明多个不同功能的拦截器,底层还是调用的grpc的拦截器。

初次使用:

package main

import (
“context”
“flag”
“fmt”
filestorageServer “go-zero-micro/rpc/code/ucenter/internal/server/filestorage”

“go-zero-micro/rpc/code/ucenter/internal/config”
ucentergormServer “go-zero-micro/rpc/code/ucenter/internal/server/ucentergorm”
ucentersqlxServer “go-zero-micro/rpc/code/ucenter/internal/server/ucentersqlx”
“go-zero-micro/rpc/code/ucenter/internal/svc”
“go-zero-micro/rpc/code/ucenter/ucenter”

“github.com/zeromicro/go-zero/core/conf”
“github.com/zeromicro/go-zero/core/service”
“github.com/zeromicro/go-zero/zrpc”
“google.golang.org/grpc”
“google.golang.org/grpc/reflection”
)

var configFile = flag.String(“f”, “conf/dev/rpc/ucenter.yaml”, “the config file”)

func main() {
flag.Parse()

var c config.Config
conf.MustLoad(*configFile, &c)
ctx := svc.NewServiceContext©

s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
ucenter.RegisterUcenterSqlxServer(grpcServer, ucentersqlxServer.NewUcenterSqlxServer(ctx))
ucenter.RegisterUcenterGormServer(grpcServer, ucentergormServer.NewUcenterGormServer(ctx))

//新增的分组接口必须要在这里注册,根据proto生成时可能未新增,否则会报 unknown service ucenter.fileStorage
ucenter.RegisterFileStorageServer(grpcServer, filestorageServer.NewFileStorageServer(ctx))

if c.Mode == service.DevMode || c.Mode == service.TestMode {
reflection.Register(grpcServer)
}

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Go
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
rver “go-zero-micro/rpc/code/ucenter/internal/server/ucentersqlx”
“go-zero-micro/rpc/code/ucenter/internal/svc”
“go-zero-micro/rpc/code/ucenter/ucenter”

“github.com/zeromicro/go-zero/core/conf”
“github.com/zeromicro/go-zero/core/service”
“github.com/zeromicro/go-zero/zrpc”
“google.golang.org/grpc”
“google.golang.org/grpc/reflection”
)

var configFile = flag.String(“f”, “conf/dev/rpc/ucenter.yaml”, “the config file”)

func main() {
flag.Parse()

var c config.Config
conf.MustLoad(*configFile, &c)
ctx := svc.NewServiceContext©

s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
ucenter.RegisterUcenterSqlxServer(grpcServer, ucentersqlxServer.NewUcenterSqlxServer(ctx))
ucenter.RegisterUcenterGormServer(grpcServer, ucentergormServer.NewUcenterGormServer(ctx))

//新增的分组接口必须要在这里注册,根据proto生成时可能未新增,否则会报 unknown service ucenter.fileStorage
ucenter.RegisterFileStorageServer(grpcServer, filestorageServer.NewFileStorageServer(ctx))

if c.Mode == service.DevMode || c.Mode == service.TestMode {
reflection.Register(grpcServer)
}

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Go
[外链图片转存中…(img-eF1WMIbo-1713128513271)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

Logo

一起探索未来云端世界的核心,云原生技术专区带您领略创新、高效和可扩展的云计算解决方案,引领您在数字化时代的成功之路。

更多推荐