A configuração de go-zero e a introdução e expansão de gorm e retornos personalizados

Dimensões de engenharia (do site oficial)

.
├── consumidor
├── go.mod
├── interno
│ └── modelo
├── trabalho
├── pkg
├── restful
├── script
└── serviço

  • consumidor: serviço de consumo de fila
  • internal: Módulos públicos acessíveis dentro do projeto
  • trabalho: serviço de trabalho cron
  • pkg: módulos públicos acessíveis fora do projeto
  • restful: diretório de serviço HTTP, no qual os microsserviços com serviço como a dimensão são armazenados
  • script: diretório de serviço de script, que armazena serviços com script como dimensão
  • serviço: diretório de serviço gRPC, no qual os microsserviços com serviço como dimensão são armazenados

Dimensões do Serviço (Catálogo de Projetos) (Extraído do site oficial)

exemplo
├── etc
│ └── exemplo.yaml
├── main.go
└── interno
    ├── config
    │ └── config.go
    ├── manipulador
    │ ├── xxxhandler.go
    │ └── xxxhandler. ir
    ├── logic
    │ └── xxxlogic.go
    ├── svc
    │ └── servicecontext.go
    └── tipos
        └── tipos.go

  • exemplo: um único diretório de serviço, geralmente o nome de um microsserviço
  • etc: Diretório do arquivo de configuração estática
  • main.go: arquivo de entrada de inicialização do programa
  • internal: Arquivos internos de um único serviço, cuja visibilidade é limitada ao serviço atual
  • config: o diretório de declaração da estrutura correspondente ao arquivo de configuração estática
  • handler: diretório do handler, opcional, geralmente o serviço http terá esta camada para gerenciamento de roteamento, que handler é um sufixo fixo
  • lógica: diretório de negócios, todos os arquivos de codificação de negócios são armazenados neste diretório, logic com um sufixo fixo
  • svc: Diretório de injeção de dependência, todas as dependências necessárias para a camada lógica devem ser explicitamente injetadas aqui
  • tipos: diretório de armazenamento de estrutura

1. Configuração básica

1. Banco de dados

1.1, docker-compose.yaml

version: '3'
services:
  mysql:
    container_name: mysql8
    image: mysql:${MYSQL_VERSION}
    restart: always
    ports:
      - ${MYSQL_PORT}:3306
    environment:
      TZ: Asia/Shanghai
      MYSQL_ROOT_PASSWORD: 123456
      MYSQL_DATABASE: zero_demo
    volumes:
      - ${MYSQL_DIR}/data:/var/lib/mysql
      - ${MYSQL_DIR}/conf:/etc/mysql/conf.d/
      - ${MYSQL_DIR}/logs:/logs
    command:
      --default-authentication-plugin=mysql_native_password
      --character-set-server=utf8mb4
      --collation-server=utf8mb4_general_ci
      --explicit_defaults_for_timestamp=true
      --lower_case_table_names=1

  Redis:
    container_name: redis6
    image: redis:${REDIS_VERSION}
    restart: always
    volumes:
      - ${REDIS_DIR}/data:/data
      - ${REDIS_DIR}/conf:/etc/redis/redis.conf
    ports:
      - ${REDIS_PORT}:6379
    command: redis-server /etc/redis/redis.conf

1.2, configuração do arquivo yaml no diretório etc

Name: demo   # 由api中的service名决定
Host: 0.0.0.0
Port: 8080

Mysql:
  DataSource: root:111111@tcp(127.0.0.1:3306)/demo?charset=utf8mb4&parseTime=true&loc=Local

#jwtAuth   go-zero 中内置了 JWT 的解密和验证功能,只需开启jwt使用即可
JwtAuth:
  AccessSecret: demo-aslfhsafsfsflaskfasf
  AccessExpire: 7200

#Redis:
#  Address: 127.0.0.1:6379
#  Pass: 123456

1.3, configuração do arquivo config.go no diretório config em interno

package config

import "github.com/zeromicro/go-zero/rest"

//yaml文件中的配置参数经过解析后会解析到此文件中,所以此文件中的参数要与yaml中的参数相对应
type Config struct {
	rest.RestConf
	Mysql struct {
		DataSource string
	}
	JwtAuth struct {
		AccessSecret string
		AccessExpire int64
	}
	//Redis struct {
	//	Address string
	//	Pass    string
	//}
}

1.4, configuração do arquivo servicecontext.go em svc

package svc

import (
	"demo/common/database"
	"demo/internal/config"
)

type ServiceContext struct {
	Config      config.Config
	UserRepo    repo.UserRepo
	//Redis  *redis.Redis
}

func NewServiceContext(c config.Config) *ServiceContext {
	dbConn := database.NewDB(c.Mysql.DataSource) //引入数据库得到数据库链接
	//newRedis := redis.New(c.Redis.Address, redisConfig(c))
	return &ServiceContext{
		Config:      c,
		UserRepo:    repo.NewUserRepo(dbConn), //调用数据库(数据库初始化,因为NewServiceContext()函数在main函数中已经调用初始化了)
		//Redis:  newRedis,
	}
}

//func redisConfig(c config.Config) redis.Option {
//	return func(r *redis.Redis) {
//		r.Type = redis.NodeType
//		r.Pass = c.Redis.Pass
//	}
//}

1.5. Apresentar a implementação do banco de dados de link gorm

package database

import (
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/schema"
)

type DBConn struct {
	ConnGorm *gorm.DB
}

// NewDB  连接并初始化数据库
func NewDB(dataSource string) *DBConn {
	db, err := gorm.Open(mysql.Open(dataSource), &gorm.Config{
		DisableForeignKeyConstraintWhenMigrating: true,
		SkipDefaultTransaction:                   false,
		NamingStrategy: schema.NamingStrategy{
			SingularTable: true, // use singular table name, table for `User` would be `user` with this option enabled
		},
	})
	if err != nil {
		panic("连接数据库失败")
	}

	d := &DBConn{
		ConnGorm: db,
	}
	InitDB(db)
	return d
}
func InitDB(db *gorm.DB) {
	if err := db.AutoMigrate(
		&User{},
	); err != nil {
		panic(err)
	}
}

1.6. Código comercial de operação de banco de dados

package repo

import (
	"demo/common/database"
	"demo/internal/types"
)

type user struct {
	db *database.DBConn
}

type UserRepo interface {
}

func NewUserRepo(conn *database.DBConn) UserRepo {
	return &user{conn}
}

2. Erro de retorno personalizado -- dados de retorno

Formato: código, msg, erro padrão de dados

 Recomenda-se usar  o uso do formato de resposta unificada de dados de código ( documento oficial extensão HTTP ), este método é o mais simples e só precisa ser substituído

2.1, erro de retorno personalizado 

Escolha um dos dois métodos de introdução a seguir na função principal. A vantagem é que você só precisa apresentá-lo uma vez

2.1.1. Oficial:

package main

import (
	"demo/internal/config"
	"demo/internal/handler"
	"demo/internal/svc"
	"flag"
	"fmt"
	"github.com/zeromicro/go-zero/core/conf"
	"github.com/zeromicro/go-zero/core/logc"
	"github.com/zeromicro/go-zero/rest"
)

var configFile = flag.String("f", "etc/demo.yaml", "the config file")

func main() {
	flag.Parse()

	//调试用,调试时使错误以plain(比较直观)的方式打印在终端
	var x logc.LogConf
	x.Encoding = "plain"
	logc.MustSetup(x)


	引入自定义返回错误,也可以引入自己构造的
	/*	httpx.SetErrorHandlerCtx(func(ctx context.Context, err error) (int, any) {
		switch e := err.(type) {
		case *errors.CodeMsg:
			return http.StatusOK, xhttp.BaseResponse[types.Nil]{
				Code: e.Code,
				Msg:  e.Msg,
			}
		default:
			return http.StatusInternalServerError, nil
		}
	})*/

	var c config.Config
	conf.MustLoad(*configFile, &c)

	server := rest.MustNewServer(c.RestConf)
	defer server.Stop()

	ctx := svc.NewServiceContext(c)
	handler.RegisterHandlers(server, ctx)

	fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)
	server.Start()
}

2.1.2: Personalizado

Código de importação (método de gravação 1):

package errorz

import (
	"fmt"
)

//**********************************
//按go-zero官方例子进行,用 /*  */ 标注这段需要配置在main函数中。

/*
httpx.SetErrorHandlerCtx(func(ctx context.Context, err error) (int, any) {
	switch e := err.(type) {
	case *errorz.CodeMsg:
		return http.StatusOK, errorz.CodeMsg[errorz.Nil]{
			Code: e.Code,
			Msg:  e.Msg,
		}
	default:
		return http.StatusInternalServerError, nil
	}
})*/

// 用来在main函数中赋值
type CodeMsg[T any] struct {
	Code int    `json:"code"`
	Msg  string `json:"msg"`
}

//*****************************************************

type StdCodeMsg struct {
	Code int    `json:"code"`
	Msg  string `json:"msg"`
}

func (c *StdCodeMsg) Error() string {
	return fmt.Sprintf("code: %d, msg: %s", c.Code, c.Msg)
}

// New creates a new StdCodeMsg.
func NewStdCodeMsg(code int, msg string) error {
	return &StdCodeMsg{Code: code, Msg: msg}
}

func (c *StdCodeMsg) StdCodeMsg() any {
	return &StdCodeMsg{
		Code: c.Code,
		Msg:  c.Msg,
	}
}

// Nil represents the predeclared value nil.
type Nil struct{}

Código de importação (método de escrita 2):

package errorz

//**********************************
//按go-zero官方例子进行,用 /*  */ 标注这段需要配置在main函数中用来返回错误。

/*httpx.SetErrorHandlerCtx(func(ctx context.Context, err error) (int, interface{}) {
	switch e := err.(type) {
	case *errorx.BizError:
	return http.StatusOK, e.Data()
	default:
	return http.StatusInternalServerError, nil
	}
})
*/

type CoMsg struct {
	Code int    `json:"code"`
	Msg  string `json:"msg"`
}

type ErrorResponse struct {
	Code int    `json:"code"`
	Msg  string `json:"msg"`
}

func NewCoMsg(code int, msg string) *CoMsg {
	return &CoMsg{
		Code: code,
		Msg:  msg,
	}
}

func (e *CoMsg) Error() string {
	return e.Msg
}

func (e *CoMsg) Data() any {
	return &ErrorResponse{
		e.Code,
		e.Msg,
	}
}

2.2. Dados de retorno personalizados (o método mais simples é o segundo método)

Método 1: As seguintes funções precisam ser chamadas na função de retorno do código comercial

// 返回数据---方式一
type RespCoMsgSuccess struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
	Data    any    `json:"data"`
}

func CoMsgSuccess(data interface{}) *RespCoMsgSuccess {
	return &RespCoMsgSuccess{200, "OK", data}
}

// 返回数据---方式二
type StdResponse[T any] struct {
	// Code represents the business code, not the http status code.
	Code int `json:"code" xml:"code"`
	// Msg represents the business message, if Code = BusinessCodeOK,
	// and Msg is empty, then the Msg will be set to BusinessMsgOk.
	Msg string `json:"msg" xml:"msg"`
	// Data represents the business data.
	Data T `json:"data,omitempty" xml:"data,omitempty"`
}

func StdSuccess(v any) StdResponse[any] {
	var resp StdResponse[any]
	resp.Code = 200
	resp.Msg = "OK"
	resp.Data = v
	return resp
}

Método 2: uso do formato de resposta unificada de dados de código ( documento oficial extensão HTTP )

Sob zeromicro, existe um x Warehouse dedicado à extensão go-zero, onde a extensão HTTP suporta:

  1. suporte ao formato de resposta de dados de código
  2. suporte de resposta xml
  3. suporte ao tipo de erro code-msg

Consulte  GitHub - zeromicro/x: Este repositório faz parte do projeto go-zero, mas fora da árvore principal. É desenvolvido sob requisitos de compatibilidade mais flexíveis do que o projeto go-zero. Este repositório faz parte do projeto go-zero, mas fora da árvore principal. É desenvolvido sob requisitos de compatibilidade mais flexíveis do que o projeto go-zero. - GitHub - zeromicro/x: Este repositório faz parte do projeto go-zero, mas fora da árvore principal. É desenvolvido sob requisitos de compatibilidade mais flexíveis do que o projeto go-zero. https://github.com/zeromicro/x

package handler

import (
    "net/http"

    "demo/internal/logic"
    "demo/internal/svc"
    "demo/internal/types"
    "github.com/zeromicro/go-zero/rest/httpx"
    xhttp "github.com/zeromicro/x/http"
)

func loginHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        var req types.LoginRequest
        if err := httpx.Parse(r, &req); err != nil {
            // httpx.ErrorCtx(r.Context(), w, err)
             // code-data 响应格式
            xhttp.JsonBaseResponseCtx(r.Context(), w, err)
            return
        }

        l := logic.NewLoginLogic(r.Context(), svcCtx)
        resp, err := l.Login(&req)
        if err != nil {
            // code-data 响应格式
            xhttp.JsonBaseResponseCtx(r.Context(), w, err)
        } else {
            // code-data 响应格式
            xhttp.JsonBaseResponseCtx(r.Context(), w, resp)
        }
    }
}

Método 3: método de modelo personalizado  oficial

Você pode chamar o método a seguir por meio do modelo personalizado oficial para geração de modelo ou pode implementá-lo em seu próprio pacote de retorno e chamá-lo no manipulador

//Handler中
func GreetHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        var req types.Request
        if err := httpx.Parse(r, &req); err != nil {
            httpx.Error(w, err)
            return
        }

        l := logic.NewGreetLogic(r.Context(), svcCtx)
        resp, err := l.Greet(&req)
        response.Response(w, resp, err)
    }
}

package response

import (
    "net/http"

    "github.com/zeromicro/go-zero/rest/httpx"
)

type Body struct {
    Code int         `json:"code"`
    Msg  string      `json:"msg"`
    Data interface{} `json:"data,omitempty"`
}

func Response(w http.ResponseWriter, resp interface{}, err error) {
    var body Body
    if err != nil {
        body.Code = -1
        body.Msg = err.Error()
    } else {
        body.Msg = "OK"
        body.Data = resp
    }
    httpx.OkJson(w, body)
}

おすすめ

転載: blog.csdn.net/fbbqt/article/details/131471522