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:
- suporte ao formato de resposta de dados de código
- suporte de resposta xml
- suporte ao tipo de erro code-msg
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)
}