go 进阶 gin实战相关: 五. gin_scaffold 企业脚手架

2023-11-20

一. gin_scaffold 企业级脚手架

  1. 为什么使用脚手架,防止重复造轮子,只关注业务开发即可
  2. gin_scaffold 优点:
  1. 提供了能够覆盖mysql/redis/request的链路功能,日志功能
  2. 支持多语言错误信息提示以及自定义错误提示
  3. 支持多环境配置
  4. 封装了log/redis/mysql/http.client常用方法等
  5. 支持swagger文档

二. gin_scaffold 脚手架安装及使用演示

  1. gin_scaffold地址: https://github.com/e421083458/gin_scaffold
    在这里插入图片描述

文件分层解释

├── README.md
├── conf            配置文件夹
│   └── dev         开发环境配置文件(多环境是创建对应其它环境的配置文件)
│       ├── base.toml
│       ├── mysql_map.toml
│       └── redis_map.toml
├── controller      控制器
│   └── demo.go
├── dao             DB数据层
│   └── demo.go
├── docs            swagger文件层
├── dto             输入输出结构层
│   └── demo.go
├── go.mod
├── go.sum
├── main.go         入口文件
├── middleware      中间件层
│   ├── panic.go
│   ├── response.go
│   ├── token_auth.go
│   └── translation.go
├── public          公共文件
│   ├── log.go
│   ├── mysql.go
│   └── validate.go
└── router          路由层
│   ├── httpserver.go
│   └── route.go
└── services        逻辑处理层

开始使用

1. 配置开启go mod 功能

参考:go 基础入门二十九 go mod

2. 下载 安装 gin_scaffold

  1. 执行clone命令下载gin_scaffold到本地,地址: git clone git@github.com:e421083458/gin_scaffold.git
  2. 下载完毕后跳转到下载的gin_scaffold目录下,
  3. 执行"go mod tidy"命令,安装gin_scaffold相关类库
  4. 使用ide编辑工具打开下载的gin_scaffold,找到"conf/mysql_map.toml","conf/redis_map.toml"配置文件,配置连接自己的mysql,redis
    在这里插入图片描述
    在这里插入图片描述
  5. 执行go run main.go 启动测试一下

实际就是拉取一个空的服务baes,测试启动一下,如果能够启动,在这个base上进行业务开发

3. 整合 golang_common

  1. gin_scaffold 下整合了golang_common包,在该包中提供了一下功能:
  1. 支持多环境运行设置,比如:dev、prod。
  2. 支持mysql、redis 多套数据源配置。
  3. 支持默认和自定义日志实例,自动滚动日志。
  4. 封装了 mysql(gorm.io/gorm v1.22.4)、redis(redigo)、http.client整合及常用方法
  5. 支持 mysql(gorm.io/gorm v1.22.4)、redis(redigo)、http.client 请求链路日志输出
    在这里插入图片描述
  1. 官方参考文档
  2. 注意在使用golang_common中提供的相关方法时,对应组件可能要提供对应的配置文件,要将这些配置文件放到项目中,官方提供了相关配置文件示例
    在这里插入图片描述
  3. 实际就是gin_scaffold 整合了一个golang_common工具包,这个工具包中封装了一些针对mysql,redis,httpclient的相关方法,整合了以后,如果在项目中使用mysql,redis时,直接通过golang_common中提供的方法使用即可,在golang_common中整合mysql,redis,httpclient时可能会用到相关的配置文件,将对应的配置文件放到项目中即可
  4. 注意: 如果拉下来的gin_scaffold脚手架项目中没有golang_common这个包,或者已经存在的项目后续开发中整合golang_common时:
  1. 项目中创建golang_common文件夹,访问官方文档地址,获取lib,log放到新建的golang_common文件夹下,
  2. 获取相关配置文件,放入conf目录下,
  3. 执行 “go get -v github.com/e421083458/golang_common” 拉取golang_common包
  4. 使用golang_common下提供的方法即可
    在这里插入图片描述

4. 测试链路

  1. 执行sql脚本,创建测试数据
CREATE TABLE `area` (
 `id` bigint(20) NOT NULL AUTO_INCREMENT,
 `area_name` varchar(255) NOT NULL,
 `city_id` int(11) NOT NULL,
 `user_id` int(11) NOT NULL,
 `update_at` datetime NOT NULL,
 `create_at` datetime NOT NULL,
 `delete_at` datetime NOT NULL,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='area';
INSERT INTO `area` (`id`, `area_name`, `city_id`, `user_id`, `update_at`, `create_at`, `delete_at`) VALUES (NULL, 'area_name', '1', '2', '2019-06-15 00:00:00', '2019-06-15 00:00:00', '2019-06-15 00:00:00');
  1. 请求接口,查看响应,其中"trace_id"就是链路唯一id
curl 'http://127.0.0.1:8880/demo/dao?id=1'
{
    "errno": 0,
    "errmsg": "",
    "data": "[{\"id\":1,\"area_name\":\"area_name\",\"city_id\":1,\"user_id\":2,\"update_at\":\"2019-06-15T00:00:00+08:00\",\"create_at\":\"2019-06-15T00:00:00+08:00\",\"delete_at\":\"2019-06-15T00:00:00+08:00\"}]",
    "trace_id": "c0a8fe445d05b9eeee780f9f5a8581b0"
}
  1. 查看链路日志
//gin_scaffold.inf.log下存放了所有正常info级别日志
tail -f gin_scaffold.inf.log
//gin_scaffold.wf.log下存放了所有的异常日志
tail -f gin_scaffold.wf.log

5. swagger文档生成

  1. 下载对应操作系统的执行文件到 G O P A T H / b i n 下面 ( 下载后解压获取到 b i n 文件夹下的 " s w a g " 文件 , 复制到 GOPATH/bin下面(下载后解压获取到bin文件夹下的"swag"文件,复制到 GOPATH/bin下面(下载后解压获取到bin文件夹下的"swag"文件,复制到GOPATH/bin下)
//如
➜  gin_scaffold git:(master) ✗ ll -r $GOPATH/bin
total 434168
-rwxr-xr-x  1 niuyufu  staff    13M  4  3 17:38 swag
  1. 编写接口,增加swagger注释,参考一下示例
// ListPage godoc
// @Summary 测试数据绑定
// @Description 测试数据绑定
// @Tags 用户
// @ID /demo/bind
// @Accept  json
// @Produce  json
// @Param polygon body dto.DemoInput true "body"
// @Success 200 {object} middleware.Response{data=dto.DemoInput} "success"
// @Router /demo/bind [post]
  1. 执行命令生成接口文档: swag init, 生成docs文件夹,生成swagger相关文件
  2. 启动服务, swagger整合完毕后访问接口文档页面: “http://127.0.0.1:8880/swagger/index.html”
  3. gin 集成swagger参考博客
  4. swagger命令报错问题

6. gin_scaffold 下conf 下配置文件

  1. base.toml 配置文件
# This is base config

[base]
    debug_mode="debug"
    time_location="Asia/Chongqing"

[http]
    addr =":8880"                       # 监听地址, default ":8700"
    read_timeout = 10                   # 读取超时时长
    write_timeout = 10                  # 写入超时时长
    max_header_bytes = 20               # 最大的header大小,二进制位长度
    allow_ip = [                        # 白名单ip列表
        "127.0.0.1",
        "192.168.1.1"
    ]

[session]
    redis_server = "127.0.0.1:6379"   #redis session server
    redis_password = ""

[log]
    log_level = "trace"         #日志打印最低级别
    [log.file_writer]           #文件写入配置
        on = true
        log_path = "./logs/go_gateway.inf.log"
        rotate_log_path = "./logs/go_gateway.inf.log.%Y%M%D%H"
        wf_log_path = "./logs/go_gateway.wf.log"
        rotate_wf_log_path = "./logs/go_gateway.wf.log.%Y%M%D%H"
    [log.console_writer]        #工作台输出
        on = false
        color = false

[cluster]
    cluster_ip="127.0.0.1"
    cluster_port="8080"
    cluster_ssl_port="4433"

[swagger]
    title="go_gateway swagger API"
    desc="This is a sample server celler server."
    host="127.0.0.1:8880"
    base_path=""
  1. mysql_map.toml配置文件
# this is mysql config
[list]
    [list.default]
        driver_name = "mysql"
        data_source_name = "root:123456@tcp(127.0.0.1:3306)/go_gateway2?charset=utf8&parseTime=true&loc=Asia%2FChongqing"
        max_open_conn = 20
        max_idle_conn = 10
        max_conn_life_time = 100
  1. redis_map.toml 配置文件
# this is redis config file
[list]
    [list.default]
        proxy_list = ["127.0.0.1:6379"]
        conn_timeout = 500
        password = ""
        db = 0
        read_timeout = 1000
        write_timeout = 1000
        max_active = 200
        max_idle = 500

7. gin_scaffold main方法启动服务解释

  1. 初始化路由,启动服务方法,与关闭服务方法
import (
	"context"
	"go_test/golang_common/lib"
	"github.com/gin-gonic/gin"
	"log"
	"net/http"
	"time"
)

var (
	HttpSrvHandler *http.Server
)

//1.初始化路由,以及监听端口启动服务
func HttpServerRun() {
	//1.默认在debug下运行
	gin.SetMode(lib.GetStringConf("base.base.debug_mode"))
	//2.初始化路由(自定义函数在下方)
	r := InitRouter()
	//3.创建server服务
	HttpSrvHandler = &http.Server{
		Addr:           lib.GetStringConf("base.http.addr"),
		Handler:        r,
		ReadTimeout:    time.Duration(lib.GetIntConf("base.http.read_timeout")) * time.Second,
		WriteTimeout:   time.Duration(lib.GetIntConf("base.http.write_timeout")) * time.Second,
		MaxHeaderBytes: 1 << uint(lib.GetIntConf("base.http.max_header_bytes")),
	}
	
	//4.启动服务
	go func() {
		log.Printf(" [INFO] HttpServerRun:%s\n",lib.GetStringConf("base.http.addr"))
		if err := HttpSrvHandler.ListenAndServe(); err != nil {
			log.Fatalf(" [ERROR] HttpServerRun:%s err:%v\n", lib.GetStringConf("base.http.addr"), err)
		}
	}()
}

//2.关闭服务方法
func HttpServerStop() {
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	if err := HttpSrvHandler.Shutdown(ctx); err != nil {
		log.Fatalf(" [ERROR] HttpServerStop err:%v\n", err)
	}
	log.Printf(" [INFO] HttpServerStop stopped\n")
}
  1. main
import (
	"flag"
	"go_test/golang_common/lib"
	"go_test/router"
	"os"
	"os/signal"
	"syscall"
)

func main(){
	//1.设置环境变量,如果第一个参数configPath为空,则会通过命令行读取"-config=./conf/prod/"下的文件
	//整合是注意此处的路径是否正确
	config := flag.String("config", "./config/dev/", "input config file like ./config/dev/")
	//lib是golang_common包下的
	lib.InitModule(*config)
	//2.退出
	defer lib.Destroy()
	
	//3.调用上面的初始化router,开启服务方法
	router.HttpServerRun()

	//4.监听系统的停止信号(也就是监听syscall.SIGINT与syscall.SIGTERM)
	quit:=make(chan os.Signal)
	signal.Notify(quit,syscall.SIGINT, syscall.SIGTERM)
	<-quit

	//5.当拿到监听的信号后说明需要关闭服务,调用上面的关闭服务方法
	router.HttpServerStop()
}

8. gin_scaffold 中初始化路由简单解释

func InitRouter(middlewares ...gin.HandlerFunc) *gin.Engine {
	//读取conf下配置文件中自定义配置数据,
	//通过gin官方提供的NewRedisStore,在redis获取session
	//store, err := sessions.NewRedisStore(10, "tcp", lib.GetStringConf("base.session.redis_server"), lib.GetStringConf("base.session.redis_password"), []byte("secret"))
	//if err != nil {
	//	log.Fatalf("sessions.NewRedisStore err:%v", err)
	//}

	//1.设置swagger相关
	docs.SwaggerInfo.Title = lib.GetStringConf("base.swagger.title")
	docs.SwaggerInfo.Description = lib.GetStringConf("base.swagger.desc")
	docs.SwaggerInfo.Version = "1.0"
	docs.SwaggerInfo.Host = lib.GetStringConf("base.swagger.host")
	docs.SwaggerInfo.BasePath = lib.GetStringConf("base.swagger.base_path")
	docs.SwaggerInfo.Schemes = []string{"http", "https"}

	//2.获取到默认的router
	router := gin.Default()
	router.Use(middlewares...)
	//3.绑定默认提供的/ping接口
	router.GET("/ping", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "pong",
		})
	})
	
	//4.绑定swagger相关接口
	router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))

	//5.设置group前缀
	adminLoginRouter := router.Group("/admin_login")
	//路由注册
	adminLoginRouter.Use( //设置中间件(下方只是示例)
		//sessions.Sessions("mysession", store), //session相关中间件
		middleware.RecoveryMiddleware(),       //用来捕获panic异常的中间件
		middleware.RequestLog(),               //打印链路日志相关的中间件
		middleware.IPAuthMiddleware(),         //权限相关中间件
		middleware.SessionAuthMiddleware(),	//校验session是否登录的中间件
		middleware.TranslationMiddleware()) //多语言翻译中间件
	{	
		//6.调用controller下的接口
		controller.AdminLoginRegister(adminLoginRouter)
	}
}

9. gin_scaffold 中controller简单示例

import (
	"encoding/json"
	"fmt"
	"github.com/e421083458/go_gateway/dao"
	"github.com/e421083458/go_gateway/dto"
	"github.com/e421083458/go_gateway/middleware"
	"github.com/e421083458/go_gateway/public"
	"github.com/e421083458/go_gateway/golang_common/lib"
	"github.com/gin-gonic/contrib/sessions"
	"github.com/gin-gonic/gin"
)

//1.创建controller结构体
type AdminController struct{}

//2.AdminController 接口体上绑定的业务方法(下方的注释时swagger相关的)
// AdminInfo godoc
// @Summary 管理员信息
// @Description 管理员信息
// @Tags 管理员接口
// @ID /admin/admin_info
// @Accept  json
// @Produce  json
// @Success 200 {object} middleware.Response{data=dto.AdminInfoOutput} "success"
// @Router /admin/admin_info [get]
func (adminlogin *AdminController) AdminInfo(c *gin.Context) {
	//1.获取session
	sess := sessions.Default(c)
	sessInfo := sess.Get(public.AdminSessionInfoKey)
	//2.在session中读取接收到的数据转换为结构体
	adminSessionInfo := &dto.AdminSessionInfo{}
	if err := json.Unmarshal([]byte(fmt.Sprint(sessInfo)), adminSessionInfo); err != nil {
		middleware.ResponseError(c, 2000, err)
		return
	}
	
	//3.设置响应
	out := &dto.AdminInfoOutput{
		ID:           adminSessionInfo.ID,
		Name:         adminSessionInfo.UserName,
	}
	middleware.ResponseSuccess(c, out)
}

//业务方法2
// ChangePwd godoc
// @Summary 修改密码
// @Description 修改密码
// @Tags 管理员接口
// @ID /admin/change_pwd
// @Accept  json
// @Produce  json
// @Param body body dto.ChangePwdInput true "body"
// @Success 200 {object} middleware.Response{data=string} "success"
// @Router /admin/change_pwd [post]
func (adminlogin *AdminController) ChangePwd(c *gin.Context) {
	//1.读取并校验请求参数
	params := &dto.ChangePwdInput{}
	if err := params.BindValidParam(c); err != nil {
		middleware.ResponseError(c, 2000, err)
		return
	}

	//2.在session读取用户信息到结构体
	sess := sessions.Default(c)
	sessInfo := sess.Get(public.AdminSessionInfoKey)
	adminSessionInfo := &dto.AdminSessionInfo{}
	if err := json.Unmarshal([]byte(fmt.Sprint(sessInfo)), adminSessionInfo); err != nil {
		middleware.ResponseError(c, 2000, err)
		return
	}

	//3.从数据库中读取 adminInfo
	//进行数据库操作时,先获取到默认的gorm连接池
	tx, err := lib.GetGormPool("default")
	if err != nil {
		middleware.ResponseError(c, 2001, err)
		return
	}
	//执行gorm操作
	adminInfo := &dao.Admin{}
	adminInfo, err = adminInfo.Find(c, tx, (&dao.Admin{UserName: adminSessionInfo.UserName}))
	if err != nil {
		middleware.ResponseError(c, 2002, err)
		return
	}

	//生成新密码 saltPassword
	saltPassword := public.GenSaltPassword(adminInfo.Salt, params.Password)
	adminInfo.Password = saltPassword

	//执行数据保存
	if err := adminInfo.Save(c, tx); err != nil {
		middleware.ResponseError(c, 2003, err)
		return
	}
	middleware.ResponseSuccess(c, "")
}


//3.当前controller注册到Group(也就是controller中第5步骤调用的方法)
func AdminRegister(group *gin.RouterGroup) {
	adminLogin := &AdminController{}
	group.GET("/admin_info", adminLogin.AdminInfo)
	group.POST("/change_pwd", adminLogin.ChangePwd)
}

10. gin_scaffold 操作数据库的dao示例

import (
	"github.com/e421083458/go_gateway/dto"
	"github.com/e421083458/go_gateway/public"
	"github.com/e421083458/gorm"
	"github.com/gin-gonic/gin"
	"github.com/pkg/errors"
	"time"
)

type Admin struct {
	Id        int       `json:"id" gorm:"primary_key" description:"自增主键"`
	UserName  string    `json:"user_name" gorm:"column:user_name" description:"管理员用户名"`
	Salt      string    `json:"salt" gorm:"column:salt" description:"盐"`
	Password  string    `json:"password" gorm:"column:password" description:"密码"`
	UpdatedAt time.Time `json:"update_at" gorm:"column:update_at" description:"更新时间"`
	CreatedAt time.Time `json:"create_at" gorm:"column:create_at" description:"创建时间"`
	IsDelete  int       `json:"is_delete" gorm:"column:is_delete" description:"是否删除"`
}

func (t *Admin) TableName() string {
	return "gateway_admin"
}

func (t *Admin) LoginCheck(c *gin.Context, tx *gorm.DB, param *dto.AdminLoginInput) (*Admin, error) {
	adminInfo, err := t.Find(c, tx, (&Admin{UserName: param.UserName, IsDelete: 0}))
	if err != nil {
		return nil, errors.New("用户信息不存在")
	}
	saltPassword := public.GenSaltPassword(adminInfo.Salt, param.Password)
	if adminInfo.Password != saltPassword {
		return nil, errors.New("密码错误,请重新输入")
	}
	return adminInfo, nil
}

func (t *Admin) Find(c *gin.Context, tx *gorm.DB, search *Admin) (*Admin, error) {
	out := &Admin{}
	err := tx.SetCtx(public.GetGinTraceContext(c)).Where(search).Find(out).Error
	if err != nil {
		return nil, err
	}
	return out, nil
}

func (t *Admin) Save(c *gin.Context, tx *gorm.DB) error {
	return tx.SetCtx(public.GetGinTraceContext(c)).Save(t).Error
}

11. gin_scaffold/middleware/下的中间件示例

  1. 捕获panic异常的中间件示例
import (
	"errors"
	"fmt"
	"github.com/e421083458/go_gateway/public"
	"github.com/e421083458/go_gateway/golang_common/lib"
	"github.com/gin-gonic/gin"
	"runtime/debug"
)

// RecoveryMiddleware捕获所有panic,并且返回错误信息
func RecoveryMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		defer func() {
			if err := recover(); err != nil {
				//先做一下日志记录
				fmt.Println(string(debug.Stack()))
				public.ComLogNotice(c, "_com_panic", map[string]interface{}{
					"error": fmt.Sprint(err),
					"stack": string(debug.Stack()),
				})
				if lib.ConfBase.DebugMode != "debug" {
					ResponseError(c, 500, errors.New("内部错误"))
					return
				} else {
					ResponseError(c, 500, errors.New(fmt.Sprint(err)))
					return
				}
			}
		}()
		c.Next()
	}
}
  1. 打印请求日志中间件示例
import (
	"bytes"
	"github.com/e421083458/go_gateway/public"
	"github.com/e421083458/go_gateway/golang_common/lib"
	"github.com/gin-gonic/gin"
	"io/ioutil"
	"time"
)

// 请求进入日志
func RequestInLog(c *gin.Context) {
	traceContext := lib.NewTrace()
	if traceId := c.Request.Header.Get("com-header-rid"); traceId != "" {
		traceContext.TraceId = traceId
	}
	if spanId := c.Request.Header.Get("com-header-spanid"); spanId != "" {
		traceContext.SpanId = spanId
	}

	c.Set("startExecTime", time.Now())
	c.Set("trace", traceContext)

	bodyBytes, _ := ioutil.ReadAll(c.Request.Body)
	c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) // Write body back

	lib.Log.TagInfo(traceContext, "_com_request_in", map[string]interface{}{
		"uri":    c.Request.RequestURI,
		"method": c.Request.Method,
		"args":   c.Request.PostForm,
		"body":   string(bodyBytes),
		"from":   c.ClientIP(),
	})
}

// 请求输出日志
func RequestOutLog(c *gin.Context) {
	// after request
	endExecTime := time.Now()
	response, _ := c.Get("response")
	st, _ := c.Get("startExecTime")

	startExecTime, _ := st.(time.Time)
	public.ComLogNotice(c, "_com_request_out", map[string]interface{}{
		"uri":       c.Request.RequestURI,
		"method":    c.Request.Method,
		"args":      c.Request.PostForm,
		"from":      c.ClientIP(),
		"response":  response,
		"proc_time": endExecTime.Sub(startExecTime).Seconds(),
	})
}

func RequestLog() gin.HandlerFunc {
	return func(c *gin.Context) {
		//todo 优化点4
		if lib.GetBoolConf("base.log.file_writer.on") {
			RequestInLog(c)
			defer RequestOutLog(c)
		}
		c.Next()
	}
}
  1. 封装响应的中间件示例
package middleware

import (
	"encoding/json"
	"fmt"
	"github.com/e421083458/go_gateway/golang_common/lib"
	"github.com/gin-gonic/gin"
	"strings"
)

type ResponseCode int

//1000以下为通用码,1000以上为用户自定义码
const (
	SuccessCode ResponseCode = iota
	UndefErrorCode
	ValidErrorCode
	InternalErrorCode

	InvalidRequestErrorCode ResponseCode = 401
	CustomizeCode           ResponseCode = 1000

	GROUPALL_SAVE_FLOWERROR ResponseCode = 2001
)

type Response struct {
	ErrorCode ResponseCode `json:"errno"`
	ErrorMsg  string       `json:"errmsg"`
	Data      interface{}  `json:"data"`
	TraceId   interface{}  `json:"trace_id"`
	Stack     interface{}  `json:"stack"`
}

func ResponseError(c *gin.Context, code ResponseCode, err error) {
	trace, _ := c.Get("trace")
	traceContext, _ := trace.(*lib.TraceContext)
	traceId := ""
	if traceContext != nil {
		traceId = traceContext.TraceId
	}

	stack := ""
	if c.Query("is_debug") == "1" || lib.GetConfEnv() == "dev" {
		stack = strings.Replace(fmt.Sprintf("%+v", err), err.Error()+"\n", "", -1)
	}

	resp := &Response{ErrorCode: code, ErrorMsg: err.Error(), Data: "", TraceId: traceId, Stack: stack}
	c.JSON(200, resp)
	response, _ := json.Marshal(resp)
	c.Set("response", string(response))
	c.AbortWithError(200, err)
}

func ResponseSuccess(c *gin.Context, data interface{}) {
	trace, _ := c.Get("trace")
	traceContext, _ := trace.(*lib.TraceContext)
	traceId := ""
	if traceContext != nil {
		traceId = traceContext.TraceId
	}

	resp := &Response{ErrorCode: SuccessCode, ErrorMsg: "", Data: data, TraceId: traceId}
	c.JSON(200, resp)
	response, _ := json.Marshal(resp)
	c.Set("response", string(response))
}

12. 将gin_scaffold脚手架修改为自己的指定服务

  1. 复制一个gin_scaffold副本出来,重命名为自己的项目,
  2. 新项目中去除拉取gin_scaffold是的git文件,以及".ide"一类的相关文件
  3. 通过ide开发工具打开, 设置go module
    在这里插入图片描述
  4. 将原项目中gin_scaffold文件名,文件路径等等全局替换掉
  5. 去除一下无用的controller,dao等等
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

go 进阶 gin实战相关: 五. gin_scaffold 企业脚手架 的相关文章

  • Netbeans 8.1 Gnome 3 GTK+ UI 字体和选项卡高度

    我刚刚在运行 GNOME 3 桌面的 Ubuntu 16 04 上安装了 NetBeans 8 1 如果可能的话 我想继续使用 IDE 的 GTK 外观和感觉 但 UI 上的字体 尤其是选项卡中的字体 太小且重叠 我尝试添加 fontsiz
  • TreeMap 删除所有大于某个键的键

    在项目中 我需要删除键值大于某个键的所有对象 键类型为Date 如果重要的话 据我所知TreeMapJava中实现的是红黑树 它是一种二叉搜索树 所以我应该得到O n 删除子树时 但除了制作尾部视图并一一删除之外 我找不到任何方法可以做到这
  • java.lang.ClassNotFoundException:javax.mail.MessagingException

    我想使用 eclipse 将电子邮件从我的 gmail 帐户发送到另一个邮件帐户 我使用 apache tomcat 7 0 34 作为我的 Web 服务器 并使用端口 8080 作为 apache 服务器 HTTP 1 1 并使用 JRE
  • 如何在java中将数组值排序为循环格式?

    我的数组值如下 String value 1 2 3 4 5 6 7 8 9 10 假设如果我将值 5 传递给 tat 数组 它应该按如下顺序排序 5 6 7 8 9 10 1 2 3 4 怎么办 有人帮忙吗 感谢你 你需要的就是所谓的轮换
  • Android:文本淡入和淡出

    我已阅读此 stackoverflow 问题和答案 并尝试实现文本淡入和淡出 Android中如何让文字淡入淡出 https stackoverflow com questions 8627211 how to make text fade
  • Thymeleaf 3 Spring 5 映射加载字符串而不是 HTML

    我正在尝试将 Spring 5 和 Thymeleaf 3 一起配置 我正在 Eclipse 上工作 我使用 全新安装 构建并使用 springboot run 运行应用程序 我已经设置了一个控制器和几个模板 但 Thymeleaf 似乎找
  • 如何调试“com.android.okhttp”

    在android kitkat中 URLConnection的实现已经被OkHttp取代 如何调试呢 OkHttp 位于此目录中 external okhttp android main java com squareup okhttp 当
  • 将巨大的模式编译成Java

    有两个主要工具提供了将 XSD 模式编译为 Java 的方法 xmlbeans 和 JAXB 问题是 XSD 模式确实很大 30MB 的 XML 文件 大部分模式在我的项目中没有使用 所以我可以注释掉大部分代码 但这不是一个好的解决方案 目
  • Runtime.exec 处理包含多个空格的参数

    我怎样才能进行以下运行 public class ExecTest public static void main String args try Notice the multiple spaces in the argument Str
  • 断言 Kafka 发送有效

    我正在使用 Spring Boot 编写一个应用程序 因此要写信给 Kafka 我这样做 Autowired private KafkaTemplate
  • 将人类日期(当地时间 GMT)转​​换为日期

    我正在服务器上工作 服务器正在向我发送 GMT 本地日期的日期 例如Fri Jun 22 09 29 29 NPT 2018在字符串格式上 我将其转换为日期 如下所示 SimpleDateFormat simpleDateFormat ne
  • 提高 PostgreSQL 1 亿数据左连接查询性能

    我在用Postgresql 9 2 version Windows 7 64 bit RAM 6GB 这是一个Java企业项目 我必须在我的页面中显示订单相关信息 有三个表通过左连接连接在一起 Tables TV HD 389772 行 T
  • 使用 Elastic Beanstalk 进行 Logback

    我在使用 Elastic Beanstalk 记录应用程序日志时遇到问题 我正在 AWS Elastic Beanstalk 上的 Tomcat 8 5 with Corretto 11 running on 64bit Amazon Li
  • JDBC 时间戳和日期 GMT 问题

    我有一个 JDBC 日期列 如果我使用 getDate 则会得到 date 仅部分2009 年 10 月 2 日但如果我使用 getTimestamp 我会得到完整的 date 2009 年 10 月 2 日 13 56 78 890 这正
  • 如何区分从 Saxon XPathSelector 返回的属性节点和元素节点

    给定 XML
  • 使用 HtmlUnit 定位弹出窗口

    我正在构建一个登录网站并抓取一些数据的程序 登录表单是一个弹出窗口 所以我需要访问这个www betexplorer com网站 在页面的右上角有一个登录链接 写着 登录 我单击该链接 然后出现登录弹出表单 我能够找到顶部的登录链接 但找不
  • 手动设置Android Studio的JDK路径

    如何为 Android Studio 使用自定义 JDK 路径 我不想弄乱 PATH 因为我没有管理员权限 是否有某个配置设置文件允许我进行设置 如果您查看项目设置 您可以从那里访问 jdk 在标准 Windows 键盘映射上 您可以在项目
  • Java 正则表达式中的逻辑 AND

    是否可以在 Java Regex 中实现逻辑 AND 如果答案是肯定的 那么如何实现呢 正则表达式中的逻辑 AND 由一系列堆叠的先行断言组成 例如 foo bar glarch 将匹配包含所有三个 foo bar 和 glarch 的任何
  • Java/Python 中的快速 IPC/Socket 通信

    我的应用程序中需要两个进程 Java 和 Python 进行通信 我注意到套接字通信占用了 93 的运行时间 为什么通讯这么慢 我应该寻找套接字通信的替代方案还是可以使其更快 更新 我发现了一个简单的修复方法 由于某些未知原因 缓冲输出流似
  • Java 和/C++ 在多线程方面的差异

    我读过一些提示 多线程实现很大程度上取决于您正在使用的目标操作系统 操作系统最终提供了多线程能力 比如Linux有POSIX标准实现 而windows32有另一种方式 但我想知道编程语言水平的主要不同 C似乎为同步提供了更多选择 例如互斥锁

随机推荐

  • 【golang】error parsing regexp: invalid or unsupported Perl syntax (正则表达式校验密码)

    要在 Go 中编写密码校验规则 确保密码不少于8位且包含数字和字母 你可以使用正则表达式和 Go 的 regexp 包来实现 以下是一个示例代码 错误示范 package main import fmt regexp func valida
  • 一、机器学习简介

    一 机器学习简介 1 1 机器学习简介 人工智能 Artificial Intelligence 简称AI 是对人的意识 思维过程进行模拟的一门新学科 如今 人工智能从虚无缥缈的科学幻想变成了现实 计算机科学家们在 机器学习 Machine
  • sc_project

    服务计算项目 总结报告 项目介绍 工作说明 实现效果 实验心得 项目介绍 本次项目是一个文章的博客 其功能有用户的登录 查找获取文章 删除文章 编辑评论 查看评论等等 除此之外用户登录时还会获得TOKEN 而用户添加评论时需要进行TOKEN
  • c++使用继承类实现异常处理

    sales h pragma once include
  • Linux Container(lxc)分析和配置使用

    前提 本文翻译 有道翻译 自linux container lxc 根据重点摘录学习 介绍 最好将容器化定义为 通过操作系统中的特性启用的进程隔离机制 容器是与系统其他部分隔离的一个或多个进程的集合 Containers VMs lxc通过
  • Node.js的基本模块,global、process详解

    基本模块 因为Node js是运行在服务区端的JavaScript环境 服务器程序和浏览器程序相比 最大的特点是没有浏览器的安全限制了 而且 服务器程序必须能接收网络请求 读写文件 处理二进制内容 所以 Node js内置的常用模块就是为了
  • 2023 华子(华为)硬件岗位面试2

    写在前面 本内容仅作参考 如有侵权或者其他问题 立马删除 也仅作为笔者个人经历或者回忆 不一定完全准确 一切都在改变 也祝愿大家面试顺利 顺利取得自己心仪的offer 编辑切换为居中 添加图片注释 不超过 140 字 可选 本次是业务主管面
  • 代码随想录算法训练营19期第42天

    01背包问题 二维 代码随想录 视频讲解 带你学透0 1背包问题 关于背包问题 你不清楚的地方 这里都讲了 动态规划经典问题 数据结构与算法 哔哩哔哩 bilibili 初步思路 动态规划 背包问题 总结 dp i j 表示从下标为 0 i
  • Spring Boot(十):Druid的监控统计和多数据源配置

    Druid的监控统计 Druid内置提供一个StatFilter 用于统计监控信息 下面我们就来做一些配置 启动Druid的监控 1 配置pom xml
  • JS阻止事件冒泡的几种方式

    JS阻止事件冒泡的几种方式 事件委托 将元素的绑定事件写起其父元素上 防止事件冒泡 example div div div div 将事件绑定在父元素上 不管子元素是不是动态生成的 将第一种绑定事件写成第二种方式 son click fun
  • Tomcat启动乱码

    修改tomcat的conf下的logging properties中的参数 java util logging ConsoleHandler encoding GBK 将UTF 8改到GBK就行了保存后重启tomcat就正常了
  • 如何调整oracle参数,使它支持更多的用户连接

    在参数文件中有三个参数 processes license max sessions license max users 这三个参数相互作用影响着用户连接数 license max sessions 同时连接数据库的会话数 license
  • 手把手教你通过端口映射,轻松搭建Windows远程桌面

    市面上有很多的远程桌面软件 如TeamViewer 向日葵等 但无一例外 它们提供的免费服务连接质量普遍不高 而付费服务价格又偏高 并不能使人满意 但众所周知 微软自带的Windows远程桌面其实在连接质量和稳定性方面一点都不输给第三方软件
  • jquery中$()的使用

    在jquery中最常使用的就是 这个符号了 在我没有系统的学习jquery之前 我用到的 都是用于对元素的选择 而这只是 的很简单的用法 在jquery 函数一共有三种用法 selector context 在这个方法中selector是选
  • error: GLES2/gl2.h: No such file or directory

    最近一个朋友让帮忙编译android程序 中间遇到了很多问题 大概都解决了 最后又遇到了一个问题 GLES2 gl2 h No such file or directory 这个问题 我大概知道是怎么回事 关键是没有指定ndk的编译版本号
  • 公司刚上市就来了个从字节拿28K的人,让我见识到了什么才是测试天花板···

    5年测试 应该是能达到资深测试的水准 即不仅能熟练地开发业务 而且还能熟悉项目开发 测试 调试和发布的流程 而且还应该能全面掌握数据库等方面的技能 如果技能再高些的话 甚至熟悉分布式组件等高级技能 或者说 做个项目小组长 管个3 4号人 应
  • router-link 和 router-view 的 关系

  • vue整合ueditor

    一 前端代码 Ueditor官网地址为 http ueditor baidu com website download html ueditor 下载好之后 将Jsp版本解压 解压后文件夹改名为ueditor 将文件夹中的jsp目录删掉 之
  • Elasticsearch7 清空指定Index 相关数据

    注意 Elasticsearch7 起 Index索引已经不支持创建指定Type 类型 默认取值为 doc Elasticsearch7 清空指定Index 语法 POST 请求 http es 服务器地址 索引名称 delete by q
  • go 进阶 gin实战相关: 五. gin_scaffold 企业脚手架

    目录 一 gin scaffold 企业级脚手架 二 gin scaffold 脚手架安装及使用演示 文件分层解释 开始使用 1 配置开启go mod 功能 2 下载 安装 gin scaffold 3 整合 golang common 4