Go自研微服务框架-参数处理

这篇具有很好参考价值的文章主要介绍了Go自研微服务框架-参数处理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

参数处理

1. 频繁创建context的优化

sync.Pool用于存储那些被分配了但是没有被使用,但是未来可能被使用的值,这样可以不用再次分配内存,提高效率。

sync.Pool大小是可伸缩的,高负载是会动态扩容,存放在池中不活跃的对象会被自动清理。

type Engine struct {
	*router
	funcMap    template.FuncMap
	HTMLRender render.HTMLRender
	pool       sync.Pool
}

func New() *Engine {
	engine := &Engine{
		router:     &router{},
		funcMap:    nil,
		HTMLRender: render.HTMLRender{},
	}
	engine.pool.New = func() any {
		return engine.allocateContext()
	}
	return engine
}
func (e *Engine) allocateContext() any {
	return &Context{engin: e}
}


func (e *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	ctx := e.pool.Get().(*Context)
	ctx.W = w
	ctx.R = r
	e.handleHttpRequest(ctx)
	e.pool.Put(ctx)
}

func (e *Engine) handleHttpRequest(ctx *Context) {

	groups := e.router.groups
	for _, g := range groups {
		uri := ctx.R.RequestURI
		routerName := SubStringLast(uri, "/"+g.groupName)
		node := g.treeNode.Get(routerName)
		if node == nil || !node.isEnd {
			ctx.W.WriteHeader(http.StatusNotFound)
			fmt.Fprintln(ctx.W, ctx.R.RequestURI+" not found")
			return
		}

		methodHandle, ok := g.handlerMap[routerName]
		if ok {
			_, ok = methodHandle[ANY]
			if ok {
				g.methodHandle(ANY, methodHandle, ctx)
				return
			}
			_, ok = methodHandle[ctx.R.Method]
			if ok {
				g.methodHandle(ctx.R.Method, methodHandle, ctx)
				return
			}
			ctx.W.WriteHeader(http.StatusMethodNotAllowed)
			fmt.Fprintln(ctx.W, ctx.R.Method+" not allowed")
		}
	}
}

2. query参数

首先我们来处理query参数,比如:http://xxx.com/user/add?id=1&age=20&username=张三

记得将路由的URL匹配改为:uri := ctx.R.URL.Path

type Context struct {
	W          http.ResponseWriter
	R          *http.Request
	engin      *Engine
	queryCache url.Values
}

func (c *Context) GetDefaultQuery(key string, defaultValue string) string {
	values, ok := c.GetQueryArray(key)
	if !ok {
		return defaultValue
	} else {
		return values[0]
	}
}


func (c *Context) GetQuery(key string) string {
	c.initQueryCache()
	return c.queryCache.Get(key)
}

func (c *Context) QueryArray(key string) (values []string) {
	c.initQueryCache()
	values, _ = c.queryCache[key]
	return
}

func (c *Context) GetQueryArray(key string) (values []string, ok bool) {
	c.initQueryCache()
	values, ok = c.queryCache[key]
	return
}

func (c *Context) initQueryCache() {
	if c.queryCache == nil {
		if c.R != nil {
			c.queryCache = c.R.URL.Query()
		} else {
			c.queryCache = url.Values{}
		}
	}
}

2.1 map类型参数

类似于http://localhost:8080/queryMap?user[id]=1&user[name]=张三

func (c *Context) QueryMap(key string) (dicts map[string]string) {
	dicts, _ = c.GetQueryMap(key)
	return
}

func (c *Context) GetQueryMap(key string) (map[string]string, bool) {
	c.initQueryCache()
	return c.get(c.queryCache, key)
}

func (c *Context) get(m map[string][]string, key string) (map[string]string, bool) {
	//user[id]=1&user[name]=张三
	dicts := make(map[string]string)
	exist := false
	for k, value := range m {
		if i := strings.IndexByte(k, '['); i >= 1 && k[0:i] == key {
			if j := strings.IndexByte(k[i+1:], ']'); j >= 1 {
				exist = true
				dicts[k[i+1:][:j]] = value[0]
			}
		}
	}
	return dicts, exist
}

3. Post表单参数

获取表单参数我们借助 http.Request.PostForm

Form属性包含了post表单和url后面跟的get参数。

PostForm属性只包含了post表单参数。


func (c *Context) initFormCache() {
	if c.formCache == nil {
		c.formCache = make(url.Values)
		req := c.R
		if err := req.ParseMultipartForm(defaultMultipartMemory); err != nil {
			if !errors.Is(err, http.ErrNotMultipart) {
				log.Println(err)
			}
		}
		c.formCache = c.R.PostForm
	}
}

func (c *Context) GetPostForm(key string) (string, bool) {
	if values, ok := c.GetPostFormArray(key); ok {
		return values[0], ok
	}
	return "", false
}

func (c *Context) PostFormArray(key string) (values []string) {
	values, _ = c.GetPostFormArray(key)
	return
}

func (c *Context) GetPostFormArray(key string) (values []string, ok bool) {
	c.initFormCache()
	values, ok = c.formCache[key]
	return
}

func (c *Context) GetPostFormMap(key string) (map[string]string, bool) {
	c.initFormCache()
	return c.get(c.formCache, key)
}

func (c *Context) PostFormMap(key string) (dicts map[string]string) {
	dicts, _ = c.GetPostFormMap(key)
	return
}

4. 文件参数

借助http.Request.FormFile


func (c *Context) FormFile(name string) (*multipart.FileHeader, error) {
	req := c.R
	if err := req.ParseMultipartForm(defaultMultipartMemory); err != nil {
		return nil, err
	}
	file, header, err := req.FormFile(name)
	if err != nil {
		return nil, err
	}
	err = file.Close()
	if err != nil {
		return nil, err
	}
	return header, nil
}
g.Post("/file", func(ctx *msgo.Context) {
		file, err := ctx.FormFile("file")
		if err != nil {
			log.Println(err)
		}
		src, err := file.Open()
		defer src.Close()
		if err != nil {
			log.Println(err)
		} else {
			out, err := os.Create("./upload/test.png")
			defer out.Close()
			if err != nil {
				log.Println(err)
			} else {
				io.Copy(out, src)
			}
		}
	})

一般会有将文件存储的需求,可以将上述的代码也提取为一个方法


func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error {
	src, err := file.Open()
	if err != nil {
		return err
	}
	defer src.Close()

	out, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer out.Close()

	_, err = io.Copy(out, src)
	return err
}
func (c *Context) MultipartForm() (*multipart.Form, error) {
	err := c.R.ParseMultipartForm(defaultMultipartMemory)
	return c.R.MultipartForm, err
}

5. json参数

现在流行前后端分离开发,前后端交互以json的形式来通信

json参数:

  1. content-type: application/json
  2. post传参

一般在写代码时,我们期望这样的处理方式:

	g.Post("/jsonParam", func(ctx *msgo.Context) {
		user := &User{}
		err := ctx.DealJson(user)
		if err == nil {
			ctx.JSON(http.StatusOK, user)
		} else {
			log.Println(err)
		}
	})
func (c *Context) DealJson(data any) error {
	body := c.R.Body
	if c.R == nil || body == nil {
		return errors.New("invalid request")
	}
	decoder := json.NewDecoder(body)
	return decoder.Decode(data)
}

测试:

{
    "name":"张三",
    "age":10,
    "addresses":[
        "北京",
        "杭州"
    ]
}
[
    {
        "name":"11",
        "age":10,
        "addresses":[
            "北京"
        ]
    }
]

如果想要实现参数中有的属性,但是对应的结构体没有,报错,也就是检查结构体是否有效

decoder.DisallowUnknownFields()

如果结构体有的属性,但是参数中没有,想要校验这种错误,又该如何做呢?

5.1 结构体校验

如果想要达到上述的效果,我们可以写一个校验器,专门来处理这样的校验。

为了实现这种效果,我们可以改变一下思路,先将所有的参数解析为map,然后和对应的结构体进行比对


func (c *Context) DealJson(data any) error {
	body := c.R.Body
	if c.R == nil || body == nil {
		return errors.New("invalid request")
	}
	decoder := json.NewDecoder(body)
	if c.DisallowUnknownFields {
		decoder.DisallowUnknownFields()
	}
	if c.IsValidate {
		err := validateRequireParam(data, decoder)
		if err != nil {
			return err
		}
	} else {
		err := decoder.Decode(data)
		if err != nil {
			return err
		}
	}
	return nil
}

func validateRequireParam(data any, decoder *json.Decoder) error {
	if data == nil {
		return nil
	}
	valueOf := reflect.ValueOf(data)
	if valueOf.Kind() != reflect.Pointer {
		return errors.New("no ptr type")
	}

	t := valueOf.Elem().Interface()
	of := reflect.ValueOf(t)
	switch of.Kind() {
	case reflect.Struct:
		mapData := make(map[string]interface{})
		_ = decoder.Decode(&mapData)
		for i := 0; i < of.NumField(); i++ {
			field := of.Type().Field(i)
			tag := field.Tag.Get("json")
			value := mapData[tag]
			if value == nil {
				return errors.New(fmt.Sprintf("filed [%s] is not exist", tag))
			}
		}
		marshal, _ := json.Marshal(mapData)
		_ = json.Unmarshal(marshal, data)
	}

	return nil
}

如果并不是所有字段都是必须的呢?

type User struct {
	Name      string   `xml:"name" json:"name" msgo:"required"`
	Age       int      `xml:"name" json:"age"`
	Addresses []string `json:"addresses"`
}
	for i := 0; i < of.NumField(); i++ {
			field := of.Type().Field(i)
			required := field.Tag.Get("msgo")
			tag := field.Tag.Get("json")
			value := mapData[tag]
			if value == nil && required == "required" {
				return errors.New(fmt.Sprintf("filed [%s] is required", tag))
			}
		}

5.2 切片数组校验


func (c *Context) DealJson(data any) error {
	body := c.R.Body
	if c.R == nil || body == nil {
		return errors.New("invalid request")
	}
	decoder := json.NewDecoder(body)
	if c.DisallowUnknownFields {
		decoder.DisallowUnknownFields()
	}
	if c.IsValidate {
		err := validateRequireParam(data, decoder)
		if err != nil {
			return err
		}
	} else {
		err := decoder.Decode(data)
		if err != nil {
			return err
		}
	}
	return nil
}

func validateRequireParam(data any, decoder *json.Decoder) error {
	if data == nil {
		return nil
	}
	valueOf := reflect.ValueOf(data)
	if valueOf.Kind() != reflect.Pointer {
		return errors.New("no ptr type")
	}
	t := valueOf.Elem().Interface()
	of := reflect.ValueOf(t)
	switch of.Kind() {
	case reflect.Struct:
		return checkParam(of, data, decoder)
	case reflect.Slice, reflect.Array:
		elem := of.Type().Elem()
		elemType := elem.Kind()
		if elemType == reflect.Struct {
			return checkParamSlice(elem, data, decoder)
		}
	default:
		err := decoder.Decode(data)
		if err != nil {
			return err
		}
	}
	return nil
}

func checkParamSlice(elem reflect.Type, data any, decoder *json.Decoder) error {
	mapData := make([]map[string]interface{}, 0)
	_ = decoder.Decode(&mapData)
	if len(mapData) <= 0 {
		return nil
	}
	for i := 0; i < elem.NumField(); i++ {
		field := elem.Field(i)
		required := field.Tag.Get("msgo")
		tag := field.Tag.Get("json")
		value := mapData[0][tag]
		if value == nil && required == "required" {
			return errors.New(fmt.Sprintf("filed [%s] is required", tag))
		}
	}
	if data != nil {
		marshal, _ := json.Marshal(mapData)
		_ = json.Unmarshal(marshal, data)
	}
	return nil
}

5.3 引入第三方校验

gin等框架在做校验时,是使用了https://github.com/go-playground/validator 组件,我们也将其集成进来

type User struct {
	Name      string   `xml:"name" json:"name" msgo:"required"`
	Age       int      `xml:"name" json:"age" validate:"required,max=50,min=18"`
	Addresses []string `json:"addresses"`
}


func (c *Context) DealJson(data any) error {
	body := c.R.Body
	if c.R == nil || body == nil {
		return errors.New("invalid request")
	}
	decoder := json.NewDecoder(body)
	if c.DisallowUnknownFields {
		decoder.DisallowUnknownFields()
	}
	if c.IsValidate {
		err := validateRequireParam(data, decoder)
		if err != nil {
			return err
		}
	} else {
		err := decoder.Decode(data)
		if err != nil {
			return err
		}
	}
	return validate(data)
}

type SliceValidationError []error

func (err SliceValidationError) Error() string {
	n := len(err)
	switch n {
	case 0:
		return ""
	default:
		var b strings.Builder
		if err[0] != nil {
			fmt.Fprintf(&b, "[%d]: %s", 0, err[0].Error())
		}
		if n > 1 {
			for i := 1; i < n; i++ {
				if err[i] != nil {
					b.WriteString("\n")
					fmt.Fprintf(&b, "[%d]: %s", i, err[i].Error())
				}
			}
		}
		return b.String()
	}
}

func validate(obj any) error {
	if obj == nil {
		return nil
	}
	value := reflect.ValueOf(obj)
	switch value.Kind() {
	case reflect.Ptr:
		return validate(value.Elem().Interface())
	case reflect.Struct:
		return validateStruct(obj)
	case reflect.Slice, reflect.Array:
		count := value.Len()
		validateRet := make(SliceValidationError, 0)
		for i := 0; i < count; i++ {
			if err := validateStruct(value.Index(i).Interface()); err != nil {
				validateRet = append(validateRet, err)
			}
		}
		if len(validateRet) == 0 {
			return nil
		}
		return validateRet
	default:
		return nil
	}
}

func validateStruct(obj any) error {
	return validator.New().Struct(obj)
}

6. 优化验证器-接口+单例

在上面 验证的时候,每次都需要使用validator.New(),会极大的浪费性能,可以使用单例来做优化,同时验证器的实现可能有多种,提供接口,便于扩展

type StructValidator interface {
	//结构体验证,如果错误返回对应的错误信息
	ValidateStruct(any) error
	//返回对应使用的验证器
	Engine() any
}
package msgo

import (
	"github.com/go-playground/validator/v10"
	"reflect"
	"sync"
)

type StructValidator interface {
	// ValidateStruct 结构体验证,如果错误返回对应的错误信息
	ValidateStruct(any) error
	// Engine 返回对应使用的验证器
	Engine() any
}

type defaultValidator struct {
	one      sync.Once
	validate *validator.Validate
}

func (d *defaultValidator) ValidateStruct(obj any) error {
	if obj == nil {
		return nil
	}
	value := reflect.ValueOf(obj)
	switch value.Kind() {
	case reflect.Ptr:
		return d.ValidateStruct(value.Elem().Interface())
	case reflect.Struct:
		return d.validateStruct(obj)
	case reflect.Slice, reflect.Array:
		count := value.Len()
		validateRet := make(SliceValidationError, 0)
		for i := 0; i < count; i++ {
			if err := d.validateStruct(value.Index(i).Interface()); err != nil {
				validateRet = append(validateRet, err)
			}
		}
		if len(validateRet) == 0 {
			return nil
		}
		return validateRet
	default:
		return nil
	}
}

func (d *defaultValidator) validateStruct(obj any) error {
	d.lazyInit()
	return d.validate.Struct(obj)
}

func (d *defaultValidator) lazyInit() {
	d.one.Do(func() {
		d.validate = validator.New()
	})
}


func (c *Context) DealJson(data any) error {
	body := c.R.Body
	if c.R == nil || body == nil {
		return errors.New("invalid request")
	}
	decoder := json.NewDecoder(body)
	if c.DisallowUnknownFields {
		decoder.DisallowUnknownFields()
	}
	if c.IsValidate {
		err := validateRequireParam(data, decoder)
		if err != nil {
			return err
		}
	} else {
		err := decoder.Decode(data)
		if err != nil {
			return err
		}
	}
	return validate(data)
}

func validate(obj any) error {
	return Validator.ValidateStruct(obj)
}

7. 多种类型参数接收-绑定器实现

在实际中,我们需要处理json参数,xml参数或者其他参数,同样,我们将其行为抽象为接口,赋予其不同实现,这样代码更加优雅,扩展维护更加方便文章来源地址https://www.toymoban.com/news/detail-806567.html

import "net/http"

type Binding interface {
	Name() string
	Bind(*http.Request, any) error
}

7.1 JSON绑定器

package binding

import "net/http"

type Binding interface {
	Name() string
	Bind(*http.Request, any) error
}

var JSON = jsonBinding{}

package bind

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"reflect"
)

type jsonBinding struct {
	DisallowUnknownFields bool
	IsValidate            bool
}

func (b jsonBinding) Name() string {
	return "json"
}

func (b jsonBinding) Bind(r *http.Request, obj any) error {
	body := r.Body
	//post 传参的内容 是放在body中的
	if body == nil {
		return errors.New("invalid request")
	}
	decoder := json.NewDecoder(body)
	if b.DisallowUnknownFields {
		decoder.DisallowUnknownFields()
	}
	if b.IsValidate {
		err := validateParam(obj, decoder)
		if err != nil {
			return err
		}
	} else {
		err := decoder.Decode(obj)
		if err != nil {
			return err
		}

	}
	return validate(obj)
}

func validateParam(obj any, decoder *json.Decoder) error {

	//反射
	valueOf := reflect.ValueOf(obj)
	//判断其是否为指针类型
	if valueOf.Kind() != reflect.Pointer {
		return errors.New("This argument must have a pointer type")
	}
	elem := valueOf.Elem().Interface()
	of := reflect.ValueOf(elem)

	switch of.Kind() {
	case reflect.Struct:
		return checkParam(of, obj, decoder)
	case reflect.Slice, reflect.Array:
		elem := of.Type().Elem()
		if elem.Kind() == reflect.Struct {
			return checkParamSlice(elem, obj, decoder)
		}
	default:
		_ = decoder.Decode(obj)
	}
	return nil
}

func checkParamSlice(of reflect.Type, obj any, decoder *json.Decoder) error {
	mapValue := make([]map[string]interface{}, 0)
	_ = decoder.Decode(&mapValue)
	for i := 0; i < of.NumField(); i++ {
		field := of.Field(i)
		name := field.Name
		jsonName := field.Tag.Get("json")
		if jsonName != "" {
			name = jsonName
		}
		required := field.Tag.Get("msgo")
		for _, v := range mapValue {
			value := v[name]
			if value == nil && required == "required" {
				return errors.New(fmt.Sprintf("filed [%s] is not exist,because [%s] is required", jsonName, jsonName))
			}
		}
	}
	b, _ := json.Marshal(mapValue)
	_ = json.Unmarshal(b, obj)
	return nil
}

func checkParam(of reflect.Value, obj any, decoder *json.Decoder) error {
	//解析为map,然后根据map中的key 进行比对
	//判断类型 结构体 才能解析为map
	mapValue := make(map[string]interface{})
	_ = decoder.Decode(&mapValue)
	for i := 0; i < of.NumField(); i++ {
		field := of.Type().Field(i)
		name := field.Name
		jsonName := field.Tag.Get("json")
		if jsonName != "" {
			name = jsonName
		}
		required := field.Tag.Get("msgo")
		value := mapValue[name]
		if value == nil && required == "required" {
			return errors.New(fmt.Sprintf("filed [%s] is not exist,because [%s] is required", jsonName, jsonName))
		}
	}
	b, _ := json.Marshal(mapValue)
	_ = json.Unmarshal(b, obj)
	return nil
}

func (c *Context) BindJson(obj any) error {
	jsonBinding := binding.JSON
	jsonBinding.DisallowUnknownFields = c.DisallowUnknownFields
	jsonBinding.IsValidate = c.IsValidate
	return c.MustBindWith(obj, jsonBinding)
}

func (c *Context) MustBindWith(obj any, bind bind.Binding) error {
	if err := c.ShouldBind(obj, bind); err != nil {
		c.W.WriteHeader(http.StatusBadRequest)
		return err
	}
	return nil
}

func (c *Context) ShouldBind(obj any, bind bind.Binding) error {
	return bind.Bind(c.R, obj)
}

7.2 XML绑定器

func (c *Context) BindXML(obj any) error {
	return c.MustBindWith(obj, binding.XML)
}
package bind

import (
	"encoding/xml"
	"net/http"
)

type xmlBinding struct {
}

func (b xmlBinding) Name() string {
	return "mxl"
}

func (b xmlBinding) Bind(r *http.Request, obj any) error {
	if r.Body == nil {
		return nil
	}
	decoder := xml.NewDecoder(r.Body)
	if err := decoder.Decode(obj); err != nil {
		return err
	}
	return validate(obj)
}


type User struct {
	Name      string   `xml:"name" json:"name" msgo:"required"`
	Age       int      `xml:"age" json:"age" validate:"required,max=50,min=18"`
	Addresses []string `json:"addresses"`
}
<User>
<name>张三</name>
<age>20</age>
</User>
g.Post("/xmlParam", func(ctx *msgo.Context) {
		user := &User{}
		//user := User{}
		err := ctx.BindXML(user)
		if err == nil {
			ctx.JSON(http.StatusOK, user)
		} else {
			log.Println(err)
		}
	})

到了这里,关于Go自研微服务框架-参数处理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 14-RPC-自研微服务框架

    RPC 框架是分布式领域核心组件,也是微服务的基础。 RPC (Remote Procedure Call)全称是远程过程调用,相对于本地方法调用,在同一内存空间可以直接通过方法栈实现调用,远程调用则跨了不同的服务终端,并不能直接调用。 RPC框架 要解决的就是远程方法调用的问题,并且实

    2024年03月08日
    浏览(54)
  • [golang 微服务] 7. go-micro框架介绍,go-micro脚手架,go-micro结合consul搭建微服务案例

    上一节讲解了 GRPC微服务集群 + Consul集群 + grpc-consul-resolver 相关的案例,知道了微服务之间通信采用的 通信协议 ,如何实现 服务的注册和发现 ,搭建 服务管理集群 ,以及服务与服务之间的 RPC通信方式 ,具体的内容包括: protobuf协议 , consul 及 docker部署consul集群 , GRPC框架 的

    2024年02月09日
    浏览(42)
  • 100天精通Golang(基础入门篇)——第23天:错误处理的艺术: Go语言实战指南

    🌷🍁 博主猫头虎🐅🐾 带您进入 Golang 语言的新世界✨✨🍁 🦄 博客首页 ——🐅🐾猫头虎的博客🎐 🐳 《面试题大全专栏》 🦕 文章图文并茂🦖生动形象🐅简单易学!欢迎大家来踩踩~🌺 🌊 《IDEA开发秘籍专栏》 🐾 学会IDEA常用操作,工作效率翻倍~💐 🌊 《100天精通

    2024年02月07日
    浏览(67)
  • Golang 打包go项目部署到linux服务器

    我们可以在终端中输入以下代码: 然后就会生成main-linux的二进制可执行文件,然后我们就可以将main-linux放到服务器中的任一目录中,然后我们就可以执行以下命令运行。 这是我们在网上可以搜索到的方法,但是我相信很多人通过这个方法尝试后发现,它并不能运行。我相信

    2024年02月16日
    浏览(67)
  • golang微服务框架特性分析及选型

    (以下框架均为go框架) 包括:Istio、go-zero、go-kit、go-kratos、go-micro、rpcx、kitex、goa、jupiter、dubbo-go、tarsgo   1、特性及使用场景 start统计截止至2024.04.01 序号 名称 特性 适用场景 stars 1 Istio Istio 是一个开源的服务网格(Service Mesh)解决方案,提供了流量管理、安全策略、监控

    2024年04月17日
    浏览(33)
  • CentOS 9 x64 使用 Nginx、Supervisor 部署 Go/Golang 服务

    在 CentOS 9 x64 系统上,可以通过以下步骤来部署 Golang 服务。 安装以下软件包: Golang:Golang 编程语言 Nginx:Web 服务器 Supervisor:进程管理工具 Git:版本控制工具 EPEL:扩展软件包 可以通过以下命令来安装: 为 Git 生成 SSH 密钥,以便于进行代码管理。可以通过以下命令来生成

    2024年02月12日
    浏览(55)
  • Golang 通过开源库 go-redis 操作 NoSQL 缓存服务器

    前置条件: 1、导入库: import ( \\\"github.com/go-redis/redis/v8\\\" ) 2、搭建哨兵模式集群 具体可以百度、谷歌搜索,网上现成配置教程太多了,不行还可以搜教程视频,跟着视频博主一步一个慢动作,慢慢整。 本文只介绍通过 “主从架构 / 哨兵模式” 访问的形式,这是因为,单个

    2024年01月23日
    浏览(51)
  • Iris微服务框架_golang web框架_完整示例Demo

    Iris简介 Iris是一款Go语言中用来开发web应用的框架,该框架支持编写一次并在任何地方以最小的机器功率运行,如Android、ios、Linux和Windows等。该框架只需要一个可执行的服务就可以在平台上运行了。 Iris框架以简单而强大的api而被开发者所熟悉。iris除了为开发者提供非常简单

    2024年01月19日
    浏览(39)
  • go-carbon 2.2.12 版本发布, 轻量级、语义化、对开发者友好的 Golang 时间处理库

    carbon 是一个轻量级、语义化、对开发者友好的 golang 时间处理库,支持链式调用。 目前已被 awesome-go 收录,如果您觉得不错,请给个 star 吧 github.com/golang-module/carbon gitee.com/golang-module/carbon 安装使用 Golang 版本大于等于1.16 Golang 版本小于1.16 更新日志 增加对荷兰语的支持 测试

    2024年02月06日
    浏览(53)
  • go-carbon v2.3.6 发布,轻量级、语义化、对开发者友好的 golang 时间处理库

    carbon 是一个轻量级、语义化、对开发者友好的 golang 时间处理库,支持链式调用。 目前已被 awesome-go 收录,如果您觉得不错,请给个 star 吧 github.com/golang-module/carbon gitee.com/golang-module/carbon 安装使用 Golang 版本大于等于 1.16 Golang 版本小于 1.16 更新日志 将日历提取出来作为独立

    2024年01月24日
    浏览(65)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包