当前位置: 首页 > news >正文

Go之封装Http请求和日志

小伙伴们,你们好呀!我是老寇!跟我一起学习封装Http请求和日志

Http请求

封装Http请求,直接使用 net/http 就行,主要是有两点需要注意,Https如何关闭校验客户端上传文件

Https如何关闭校验

// 跳过TLS证书校验
client := &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true},},
}

客户端上传文件

上传文件需遵循 RFC 1867 标准,因此,请求头 Content-Type设为 multipart/form-data;boundary=xxx

http.go

import ("bytes""crypto/tls""errors""io""mime/multipart""net/http"
)func SendRequest(method, url string, param io.Reader, header map[string]string) (*http.Response, error) {request, err := http.NewRequest(method, url, param)if err != nil {return nil, errors.New("创建 Request 失败,错误信息:" + err.Error())}if header != nil {for k, v := range header {request.Header.Set(k, v)}}return sendHttpRequest(request)
}func GetFormFile(fileName string, buf []byte) (io.Reader, string, error) {body := &bytes.Buffer{}writer := multipart.NewWriter(body)part, err := writer.CreateFormFile("file", fileName)if err != nil {return nil, "", errors.New("创建 FormFile 失败,错误信息:" + err.Error())}_, err = io.Copy(part, bytes.NewReader(buf))if err != nil {return nil, "", errors.New("复制字节数组失败,错误信息:" + err.Error())}err = writer.Close()if err != nil {return nil, "", errors.New("关闭 Writer 失败,错误信息:" + err.Error())}return body, writer.FormDataContentType(), nil
}func SendRequestAndGetBody(method, url string, param io.Reader, header map[string]string) ([]byte, error) {response, err := SendRequest(method, url, param, header)if err != nil {return nil, err}body, err := io.ReadAll(response.Body)if err != nil {return nil, errors.New("读取响应失败,错误信息:" + err.Error())}defer response.Body.Close()return body, nil
}func sendHttpRequest(request *http.Request) (*http.Response, error) {client := &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true},},}response, err := client.Do(request)if err != nil {return nil, errors.New("发送 HTTP 请求失败,错误信息:" + err.Error())}return response, nil
}

http_test.go

func Test_Http(t *testing.T) {// Get请求header = map[string]string{"Cookie": 'token=123',}_, _ = SendRequest(http.MethodGet, "https://localhost/api/test", nil ,header)// Post请求_, _ = SendRequest(http.MethodPost, "https://localhost/api/test", strings.NewReader("username=a&password=123") ,header)// Post上传文件【表单】// 这里便于演示,随便定义一个字节数组buf := []byte('123')formFile, contentType, err := core.GetFormFile("image.jpg", buf)if err != nil {fmt.println(err.Error())return}header = map[string]string{"Content-Type": contentType,}// 上传文件_, _ = SendRequest(http.MethodPost, "https://localhost/api/upload", formFile, header)
}

日志

日志对于一个项目来说是非常重要的,因此,很有必要根据业务进行挑选,go的常用日志包有gloglogruszap

维度glog (Google)logrus (社区维护)zap (Uber)
项目背景Kubernetes 等基础设施项目常用,轻量级早期 Go 社区主流,已停止更新(仅维护)高性能工业级实践,活跃维护
性能中等(基于标准库优化)较低(反射序列化,内存分配多)极高(预分配内存、零反射,每秒百万级日志)
日志级别4 级(Info/Warning/Error/Fatal)6 级(Trace/Debug/Info/Warn/Error/Fatal)7 级(Debug/Info/Warn/Error/Dpanic/Panic/Fatal)
结构化日志❌ 不支持(仅文本格式)✅ 支持(WithFields 动态类型)✅ 强类型(zap.String()),SugaredLogger 兼容非结构化
API 风格类似标准库(如 glog.Info("msg")链式调用(logrus.WithField().Info()显式类型(zap.Info("msg", zap.String("k","v"))
日志轮转❌ 需手动实现或第三方库❌ 依赖插件(如 file-rotatelogs)2✅ 原生支持(集成 lumberjack
内存分配较少较多(反射+动态字段)极少(预分配+非反射)
动态调级❌ 不支持✅ 需手动实现2✅ 原生支持(AtomicLevel
学习成本低(类似标准库)低(类似 fmt中(结构化 API 稍复杂,SugaredLogger 简化)
适用场景轻量级工具、K8s 生态兼容旧项目维护、插件依赖型应用高并发服务、云原生、性能敏感场景

考虑到网关设备,为了节约内存空间和极致的性能,我们采用zap封装,zap不支持日志滚动,所以,我们使用第三方组件 timberjack增强,支持日志滚动,日志保留天数,日志级别

log.go

import ("errors""github.com/DeRuina/timberjack""go.uber.org/zap""go.uber.org/zap/zapcore""log""time"
)const (PROD = "prod"
)type LogConfig struct {// 日志级别Level string `yaml:"level"`// 环境Profile string `yaml:"profile"`// 日志格式Pattern string `yaml:"pattern"`// 日志文件路径FilePath string `yaml:"file-path"`// 日志最大容量【单位M】MaxSize int `yaml:"max-size"`// 日志最大数量MaxBackups int `yaml:"max-backups"`// 日志保留时间【单位天】MaxAge int `yaml:"max-age"`// 是否压缩Compress bool `yaml:"compress"`// 本地时间,默认值:false(使用UTC)LocalTime bool `yaml:"local-time"`// json格式JsonFormat bool `yaml:"json-format"`// 转换频率RotationInterval time.Duration `yaml:"rotation-interval"`// 转换时间【分钟】RotateAtMinutes []int `yaml:"rotate-at-minutes"`// 日志时间格式BackupTimeFormat string `yaml:"backup-time-format"`
}func (c *LogConfig) InitLogger() (*zap.Logger, error) {timberjackLogger := &timberjack.Logger{Filename:         c.FilePath,MaxSize:          c.MaxSize,MaxBackups:       c.MaxBackups,MaxAge:           c.MaxAge,Compress:         c.Compress,LocalTime:        c.LocalTime,RotationInterval: c.RotationInterval,RotateAtMinutes:  c.RotateAtMinutes,BackupTimeFormat: c.BackupTimeFormat,}log.SetOutput(timberjackLogger)defer timberjackLogger.Close()writeSyncer := zapcore.AddSync(timberjackLogger)// 配置日志级别levelConfig := zap.NewAtomicLevel()level, err := zapcore.ParseLevel(c.Level)if err != nil {return nil, errors.New("日志级别不存在,请重新配置,错误信息:" + err.Error())}levelConfig.SetLevel(level)// 配置环境var encoderConfig zapcore.EncoderConfigswitch c.Profile {case PROD:encoderConfig = zap.NewProductionEncoderConfig()default:encoderConfig = zap.NewDevelopmentEncoderConfig()}// 设置时间格式encoderConfig.EncodeTime = c.customTimeEncodervar encoder zapcore.Encoder// Json格式if c.JsonFormat {encoder = zapcore.NewJSONEncoder(encoderConfig)} else {encoder = zapcore.NewConsoleEncoder(encoderConfig)}core := zapcore.NewCore(encoder, writeSyncer, levelConfig)return zap.New(core, zap.AddCaller()), nil
}func (c *LogConfig) customTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {enc.AppendString(t.Format(c.Pattern))
}

application.yaml

log:# 级别level: error# 环境profile: prod# 日志格式pattern: 2006-01-02 15:04:05.000# 日志文件路径file-path: test.log# 日志最大容量【单位M】max-size: 500# 日志最大数量max-backups: 10# 日志保留时间【单位天】max-age: 30# 是否压缩compress: true# 本地时间,默认值:false(使用UTC)local-time: true# json格式,true为json格式日志json-format: false# 转换频率rotation-interval: 24h# 转换时间【分钟】rotate-at-minutes:- 0- 15- 30- 45# 日志时间格式backup-time-format: 2006-01-02-15-04-05

config.go

import ("errors""gopkg.in/yaml.v3""os"
)type SystemConfig struct {Log LogConfig `yaml:"log"`
}func GetSystemConfig(path string) (*SystemConfig, error) {data, err := os.ReadFile(path)if err != nil {return nil, errors.New("读取配置文件失败,错误信息:" + err.Error())}sysConfig := &SystemConfig{}err = yaml.Unmarshal(data, sysConfig)if err != nil {return nil, errors.New("配置文件反序列化失败,错误信息:" + err.Error())}return sysConfig, nil
}

log_test.go

import ("testing"
)func Test_Log(t *testing.T) {config, err := GetSystemConfig("application.yaml")if err != nil {t.Error(err.Error())return}logger, err := config.Log.InitLogger()if err != nil {t.Error(err.Error())return}logger.Info("信息")logger.Warn("警告")logger.Error("错误")t.Log("日志测试通过")
}

注意:部署到网关上面,日志无法打印,需要对日志增加权限和不能以sudo运行

# 增加日志权限
sudo chmod -R 7777 /home/a/app/logs
# 运行项目,不能以sudo运行项目,否则日志无法打印,这是我遇到的坑
nohup /home/a/app/log > /dev/null 2>&1 &

我是老寇,我们下次再见啦!

http://www.lryc.cn/news/618194.html

相关文章:

  • mysql登录失败 ERROR1698
  • Elasticsearch Node.js 客户端连接指南(Connecting)
  • 实现一个二维码让 iOS 和 Android 用户自动跳转到对应下载链接
  • java面试题储备4: 谈谈对es的理解
  • 基于跨平台的svg组件编写一个svg编辑器
  • 【狂热算法篇】探寻图论幽径之SPFA算法:图论迷宫里的闪电寻径者(通俗易懂版)
  • 【门诊进销存出入库管理系统】佳易王医疗器械零售进销存软件:门诊进销存怎么操作?系统实操教程 #医药系统进销存
  • 需求分发机制如何设定
  • 飞算 JavaAI 电商零售场景实践:从订单峰值到供应链协同的全链路技术革新
  • 元器件--自恢复保险丝
  • 疏老师-python训练营-Day43复习日
  • 基于大数据的在线教育评估系统 Python+Django+Vue.js
  • 【代码随想录day 18】 力扣 501.二叉搜索树中的众数
  • 我的 LeetCode 日记:Day 35 - 解构动态规划,初探 0/1 背包问题
  • 如何检查pip版本
  • Spring Boot项目中调用第三方接口
  • 【Unity】GraphicRaycaster点击失效问题
  • 邦纳BANNER相机视觉加镜头PresencePLUSP4 RICOH FL-CC2514-2M工业相机
  • 一周学会Matplotlib3 Python 数据可视化-绘制饼状图(Pie)
  • 【Activiti】要点初探
  • SQL tutorials
  • 当 GitHub 宕机时,我们如何协作?
  • 【C#】正则表达式
  • 计算机视觉(4)-相机基础知识恶补
  • 计算机网络2-3:传输方式
  • 集合,完整扩展
  • AWS EKS 常用命令大全:从基础管理到高级运维
  • 面试八股之从Java到JVM层面深入解析ReentrantLock实现原理
  • c++的四种类型转换(static_cast,reinterpret_cast,const_cast,dynamic_cast)详解和代码示例
  • 【R语言数据分析开发指南】