Makefile

build: clean goget
    go-1.17 build  -o run cmd/main/main.go

clean:
    go-1.17 clean --modcache

# 启用go module 需使用该命令更新依赖
goget:
    go-1.17 env -w GOPROXY="https://goproxy.cn,direct"
    go-1.17 env -w GOPRIVATE="*.xx.com"
    go-1.17 env -w GONOPROXY="*.xx.com"
    go-1.17 env -w GONOSUMDB="*.xxx.com"
    go-1.17 mod tidy

# 开发环境运行
run: build
    rm -rf /var/www/go_user
    mkdir /var/www/go_user
    cp run /var/www/go_user/
    cp -r config /var/www/go_user/
    docker-compose -f ./docker/docker-compose.yml up

# 静态代码检测 https://pkg.go.dev/cmd/vet
vet: goget
    go-1.17 vet ./...

main.go


var engine *gin.Engine

func main() {
    Init()
    if len(os.Args) == 1 {
        engine = routers.Init(routers.Health, router.Routers)
        if err := engine.Run(config.AppConfig.Listen); err != nil {
            zap_logger.Error(nil,xxx, fmt.Sprintf("startup service failed, err:%v\n", err))
        }
    } else {
        // 有参数就执行命令行程序
        base_routers.Cli()
        cli.Execute()
    }
}
// 初始化都放这里
func Init() {
    configFilename := ""
    if !config.IsOnline() {
        configFilename = fmt.Sprintf("%s/config/app_test.yaml", xxx)
        gin.SetMode(gin.DebugMode)
    } else {
        configFilename = fmt.Sprintf("%s/config/app_prod.yaml",xxx)
        gin.SetMode(gin.ReleaseMode)
    }
    config.AppConfig = config.ParseConfig(configFilename)
    if config.AppConfig == nil {
        panic("init failed configFilename:" + configFilename)
    }
    zap_logger.Init()
    database.MysqlInit()
    redis.RedisInit()
    // 一个端口开启 pprof+charts+prometheus
    prometheus.AddPrometheus()
    monitor.StartPprof()
}

AddPrometheus

func AddPrometheus() {
    go func() {
        defer func() {
            if err := recover(); err != nil {
                zap_logger.Errorf(nil, logTag, "prometheus err: %+v", err)
            }
        }()
        server := http.NewServeMux()
    
        server.Handle("/metrics", promhttp.Handler())
        // start an http server using the mux server
        zap_logger.Errorf(nil, logTag, "prometheus error:%v", http.ListenAndServe(":8000", server))
    }()
}

StartPprof

func StartPprof() {
    // dev test
    if config.IsDev() || config.IsTest() {
        go func() {
            defer func() {
                if err := recover(); err != nil {
                    zap_logger.Errorf(nil, logTag, "StartPprof %+v", err)
                }
            }()
            //pprof, go tool pprof -http=:16061 http://localhost:8081/debug/pprof/heap
            // http://localhost:8081/debug/charts
            zap_logger.Errorf(nil, logTag, "prometheus error:%v", http.ListenAndServe(":8081", nil))
        }()
    }
}

zap_logger

配置

log:
  default:
    level: debug
    file: /logs/gouser.log
    backup_num: 1
    max_size: 2000
    retain_days: 1

初始化

const (
    logTmFmtWithMS = "2006-01-02 15:04:05.000"
)

var Logger *zap.Logger
var ZapWriter zapcore.WriteSyncer

// Init 配置日志模块
func Init() {

    defaultLog := config.AppConfig.LogConf.Default
    var level zapcore.Level

    if level.UnmarshalText([]byte(defaultLog.Level)) != nil {
        level = zapcore.InfoLevel
    }

    encoderConfig := zapcore.EncoderConfig{
        
        MessageKey: "msg",
        LineEnding: zapcore.DefaultLineEnding,
        EncodeDuration: zapcore.StringDurationEncoder,
    }
    ZapWriter = zapcore.AddSync(&lumberjack.Logger{
        Filename:   defaultLog.File,
        MaxSize:    defaultLog.MaxSize, // megabytes
        MaxBackups: defaultLog.BackupNum,
        MaxAge:     defaultLog.RetainDays, // days
        LocalTime:  true,
    })
    core := zapcore.NewTee(zapcore.NewCore(
        zapcore.NewConsoleEncoder(encoderConfig), zapcore.AddSync(ZapWriter), level))
    Logger = zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1))
}

// 后续就可以全局引用 Logger 进行日志打印了

routers

package routers

import (
    "github.com/gin-gonic/gin"
)

type Option func(*gin.Engine)

var options = []Option{}

// 注册app的路由配置
func Include(opts ...Option) {
    options = append(options, opts...)
}

func RegisterHandler(routerGroup *gin.RouterGroup, path string, handler gin.HandlerFunc) {
    routerGroup.GET(path, handler)
    routerGroup.POST(path, handler)
}

// 只支持Post
func RegisterPostHandler(routerGroup *gin.RouterGroup, path string, handler gin.HandlerFunc) {
    routerGroup.POST(path, handler)
}

// 初始化
func Init(opts ...Option) *gin.Engine {
    // 加载多个APP的路由配置
    Include(opts...)
    r := gin.New()
    for _, opt := range options {
        opt(r)
    }
    r.SetTrustedProxies(nil)
    return r
}

cli


// CobraRootCmd represents the base command when called without any subcommands
var CobraRootCmd = &cobra.Command{
    Use:   "root_cmd",
    Short: "A brief description of your application",
    Long: `A longer description that spans multiple lines and likely contains
examples and usage of using your application. For example:

Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
    // Uncomment the following line if your bare application
    // has an action associated with it:
    // Run: func(cmd *cobra.Command, args []string) { },
}

// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the CobraRootCmd.
func Execute() {
    err := CobraRootCmd.Execute()
    if err != nil {
        os.Exit(1)
    }
}


// cli cmd
func Cli() {
    // cron
    cli.CobraRootCmd.AddCommand(crontab.ClientCmd)

    // worker
    cli.CobraRootCmd.AddCommand(worker.WorkerCmd)
}

controller

type userController struct {
    *controller.BaseController
}

var UserController = new(userController)

//按需获取用户基本信息
func (ctr *userController) GetUserInfosAction(ctx *gin.Context) {

    params := &common.Params{}
    if ctx.ShouldBind(params) != nil || (!params.Validate()) {
        ctr.Fail(ctx, nil, bbt_error.PARAMS_INVALID_ERROR_MSG)
        return
    }
    result := user.GetUserService().GetUserInfos(ctx, params)
    ctr.Success(ctx, result)
}