🐍 一款小巧的基于Go构建的开发框架,可以快速构建API服务或者Web网站进行业务开发,遵循SOLID设计原则

🐍 snake

GitHub Workflow Status codecov GolangCI godoc OpenTracing Badge Go Report Card gitmoji License

一款适合于快速开发业务的 Go 框架,可快速构建 API 服务 或 Web 网站。

Pro Tip: 每个目录下基本都有 README,可以让框架使用起来更轻松 ^_^

设计思想和原则

框架中用到的设计思想和原则,尽量满足 "高内聚、低耦合",主要遵从下面几个原则

    1. 单一职责原则
    1. 基于接口而非实现编程
    1. 依赖注入
    1. 多用组合
    1. 迪米特法则

迪米特法则: 不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口

技术栈

  • 框架路由使用 Gin 路由
  • 中间件使用 Gin 框架的中间件
  • 数据库组件 GORM
  • 文档使用 Swagger 生成
  • 配置文件解析库 Viper
  • 使用 JWT 进行身份鉴权认证
  • 校验器使用 validator 也是 Gin 框架默认的校验器
  • 任务调度 cron
  • 包管理工具 Go Modules
  • 测试框架 GoConvey
  • CI/CD GitHub Actions
  • 使用 GolangCI-lint 进行代码检测
  • 使用 make 来管理 Go 工程
  • 使用 shell(admin.sh) 脚本来管理进程
  • 使用 YAML 文件进行多环境配置

📗 目录结构

├── Makefile                     # 项目管理文件
├── build                        # 编译目录
├── cmd                          # 脚手架目录
├── config                       # 配置文件统一存放目录
├── docs                         # Swagger 文档,执行 swag init 生成的
├── handler                      # 控制器目录,用来读取输入、调用业务处理、返回结果
├── internal                     # 业务目录
│   ├── cache                    # 基于业务封装的cache
│   ├── idl                      # 数据结构转换
│   ├── model                    # 数据库 model
│   ├── repository               # 数据访问层
│   └── service                  # 业务逻辑层
├── logs                         # 存放日志的目录
├── main.go                      # 项目入口文件
├── pkg                          # 公共的 package
├── router                       # 路由及中间件目录
└── scripts                      # 存放用于执行各种构建,安装,分析等操作的脚本

🛠️ 快速开始

方式一

直接Clone项目的方式,文件比较全

TIPS: 需要本地安装MySQL数据库和 Redis

# 下载安装,可以不用是 GOPATH
git clone https://github.com/1024casts/snake

# 进入到下载目录
cd snake

# 生成环境配置文件
cd config
cp config.local.yaml config.{ENV}.yaml

# 编译
make build

# 运行
./scripts/admin.sh start

方式二

使用脚手架,仅生成基本目录, 不包含pkg等部分公共模块目录

# 下载
go get github.com/1024casts/snake/cmd/snake

export GO111MODULE=on
# 或者在.bashrc 或 .zshrc中加入
# source .bashrc 或 source .zshrc

# 使用
snake new snake-demo 
# 或者 
snake new github.com/foo/bar

💻 常用命令

  • make help 查看帮助
  • make dep 下载 Go 依赖包
  • make build 编译项目
  • make gen-docs 生成接口文档
  • make test-coverage 生成测试覆盖
  • make lint 检查代码规范

🏂 模块

公共模块

  • 图片上传(支持本地、七牛)
  • 短信验证码(支持七牛)

用户模块

  • 注册
  • 登录(邮箱登录,手机登录)
  • 发送手机验证码(使用七牛云服务)
  • 更新用户信息
  • 关注/取消关注
  • 关注列表
  • 粉丝列表

📝 接口文档

http://localhost:8080/swagger/index.html

开发规范

遵循: Uber Go 语言编码规范

📖 开发规约

🚀 部署

单独部署

上传到服务器后,直接运行命令即可

./scripts/admin.sh start

Docker 部署

如果安装了 Docker 可以通过下面命令启动应用:

# 运行
docker-compose up -d

# 验证
http://127.0.0.1/health

Supervisord

编译并生成二进制文件

go build -o bin_snake

如果应用有多台机器,可以在编译机器进行编译,然后使用rsync同步到对应的业务应用服务器

以下内容可以整理为脚本

export GOROOT=/usr/local/go1.13.8
export GOPATH=/data/build/test/src
export GO111MODULE=on
cd /data/build/test/src/github.com/1024casts/snake
/usr/local/go1.13.8/bin/go build -o /data/build/bin/bin_snake -mod vendor main.go
rsync -av /data/build/bin/ x.x.x.x:/home/go/snake
supervisorctl restart snake

这里日志目录设定为 /data/log 如果安装了 Supervisord,可以在配置文件中添加下面内容(默认:/etc/supervisor/supervisord.conf):

[program:snake]
# environment=
directory=/home/go/snake
command=/home/go/snake/bin_snake
autostart=true
autorestart=true
user=root
stdout_logfile=/data/log/snake_std.log
startsecs = 2
startretries = 2
stdout_logfile_maxbytes=10MB
stdout_logfile_backups=10
stderr_logfile=/data/log/snake_err.log
stderr_logfile_maxbytes=10MB
stderr_logfile_backups=10

重启 Supervisord

supervisorctl restart snake

📜 CHANGELOG

🏘️ 谁在用

💬 Discussion

🔋 JetBrains 开源证书支持

snake 项目一直以来都是在 JetBrains 公司旗下的 GoLand 集成开发环境中进行开发,基于 free JetBrains Open Source license(s) 正版免费授权,在此表达我的谢意。

📄 License

MIT. See the LICENSE file for details.

Comments
  • Docker 运行报错,panic: config file not found

    Docker 运行报错,panic: config file not found

    2021/11/28 14:01:51 maxprocs: Leaving GOMAXPROCS=12: CPU quota undefined app yaml panic: config file not found

    goroutine 1 [running]: main.main() /go/src/github.com/go-eagle/eagle/main.go:66 +0x7fa

  •  master 代码触发500服务错误时会panic,另外发现这个gin的模板引擎,都要继承master.html,不然就报错

    master 代码触发500服务错误时会panic,另外发现这个gin的模板引擎,都要继承master.html,不然就报错

    TemplateEngine render read name:error/404, path:D:\goworkspace\src\gin-project\internal\templates\error\404.html, error: open D:\goworkspace\src\gin-project\internal\templates\error\404.html: The system cannot find the path specified

  • 无法正常启动服务,并且程序不能按配置使用8080端口

    无法正常启动服务,并且程序不能按配置使用8080端口

    你好,我按照readme的提示通过clone的方式安装了整个框架,并且复制了一份本地配置conf/config.local.yaml,修改了数据库的密码和日志存放位置,本地redis是默认的,安装好依赖之后,程序已经能正常启动,但是不能进行web访问。另外,配置设置的是8080端口,但似乎程序使用的是1234端口,具体见下方的截图: WechatIMG295程序运行截图 WechatIMG2968080截图 WechatIMG2971234截图 WechatIMG299配置截图

    (个人认为操作上应该没有什么问题,不过也可能存在姿势不对的情况,跪求大佬指点) go version go1.14.6 darwin/amd64

  • 关于cache的一些疑问

    关于cache的一些疑问

    (新手轻拍) 看了您的代码,pkg/cache封装了memory和redis,对外提供driver,然后在internal的cache包中针对不同业务使用cache,那么我看到internal/service/vcode中,要把验证码存放到缓存中,直接调用了pkg/redis中的client,为什么不通过封装好的cache接口来调用

  • 关于service层的疑惑

    关于service层的疑惑

    目前的结构在service层写了所有的方法,每次调用时会比较难找到自己想要的方法 比如 目前的架构中 定义了 var ( UserSvc *Service VCodeSvc *Service ) 但是在handler中调用service.UserSvc时,ide也会提示VCodeSvc下的相关方法,有优化的方式吗

  • 访问注册和登陆页面报错

    访问注册和登陆页面报错

    报错信息:

    2020/10/09 16:03:36 [Recovery] 2020/10/09 - 16:03:36 panic recovered:
    GET /register HTTP/1.1
    Host: 127.0.0.1:8081
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
    Accept-Encoding: gzip, deflate, br
    Accept-Language: zh-CN,zh;q=0.9,zh-TW;q=0.8,en;q=0.7
    Connection: keep-alive
    Referer: http://127.0.0.1:8081/
    Sec-Fetch-Dest: document
    Sec-Fetch-Mode: navigate
    Sec-Fetch-Site: same-origin
    Sec-Fetch-User: ?1
    Upgrade-Insecure-Requests: 1
    User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36
    
    
    TemplateEngine render read name:error/404, path:/Users/weiwang/go/src/snake/internal/templates/error/404.html, error: open /Users/weiwang/go/src/snake/internal/templates/error/404.html: no such file or directory
    /Users/weiwang/go/pkg/mod/github.com/gin-gonic/[email protected]/context.go:842 (0x17911f2)
    	(*Context).Render: panic(err)
    /Users/weiwang/go/pkg/mod/github.com/gin-gonic/[email protected]/context.go:851 (0x1791298)
    	(*Context).HTML: c.Render(code, instance)
    /Users/weiwang/go/src/snake/app/web/error.go:10 (0x199c85d)
    	Error404: c.HTML(http.StatusOK, "error/404", gin.H{
    /Users/weiwang/go/src/snake/router/web.go:25 (0x199c771)
    	LoadWebRouter.func1: web.Error404(c)
    /Users/weiwang/go/pkg/mod/github.com/gin-gonic/[email protected]/context.go:161 (0x178d3ba)
    	(*Context).Next: c.handlers[c.index](c)
    /Users/weiwang/go/src/snake/router/middleware/requestid.go:25 (0x1990c71)
    	RequestID.func1: c.Next()
    /Users/weiwang/go/pkg/mod/github.com/gin-gonic/[email protected]/context.go:161 (0x178d3ba)
    	(*Context).Next: c.handlers[c.index](c)
    /Users/weiwang/go/src/snake/router/middleware/header.go:24 (0x198fa9c)
    	Options: c.Next()
    /Users/weiwang/go/pkg/mod/github.com/gin-gonic/[email protected]/context.go:161 (0x178d3ba)
    	(*Context).Next: c.handlers[c.index](c)
    /Users/weiwang/go/src/snake/router/middleware/header.go:16 (0x198fa46)
    	NoCache: c.Next()
    /Users/weiwang/go/pkg/mod/github.com/gin-gonic/[email protected]/context.go:161 (0x178d3ba)
    	(*Context).Next: c.handlers[c.index](c)
    /Users/weiwang/go/pkg/mod/github.com/gin-gonic/[email protected]/recovery.go:83 (0x17a1273)
    	RecoveryWithWriter.func1: c.Next()
    /Users/weiwang/go/pkg/mod/github.com/gin-gonic/[email protected]/context.go:161 (0x178d3ba)
    	(*Context).Next: c.handlers[c.index](c)
    /Users/weiwang/go/pkg/mod/github.com/gin-gonic/[email protected]/logger.go:241 (0x17a03a0)
    	LoggerWithConfig.func1: c.Next()
    /Users/weiwang/go/pkg/mod/github.com/gin-gonic/[email protected]/context.go:161 (0x178d3ba)
    	(*Context).Next: c.handlers[c.index](c)
    /Users/weiwang/go/pkg/mod/github.com/gin-gonic/[email protected]/gin.go:445 (0x1797747)
    	serveError: c.Next()
    /Users/weiwang/go/pkg/mod/github.com/gin-gonic/[email protected]/gin.go:438 (0x179739f)
    	(*Engine).handleHTTPRequest: serveError(c, http.StatusNotFound, default404Body)
    /Users/weiwang/go/pkg/mod/github.com/gin-gonic/[email protected]/gin.go:367 (0x1796d2d)
    	(*Engine).ServeHTTP: engine.handleHTTPRequest(c)
    /usr/local/go/src/net/http/server.go:2802 (0x14093d3)
    	serverHandler.ServeHTTP: handler.ServeHTTP(rw, req)
    /usr/local/go/src/net/http/server.go:1890 (0x1404c74)
    	(*conn).serve: serverHandler{c.server}.ServeHTTP(w, w.req)
    /usr/local/go/src/runtime/asm_amd64.s:1357 (0x1061280)
    	goexit: BYTE	$0x90	// NOP
    

    此外,我发现使用 db.sql 导入数据库会报错,不清楚是否有关系?

  • fix: 增删空白,修正专有名词

    fix: 增删空白,修正专有名词

    • 插入必要的空白和空行,删除不要的空白和空行。
      • 插入「盘古之白」,参考这篇文章
      • Markdown 语法元素之间插入一个空行,删除多余空行。
      • 所有 Markdown 元素顶格书写,不留缩进。
      • 删除所有行尾空白。
    • 根据「名从主人」原则,统一各技术产品名词的拼写形式,与各自官网保持一致。
    • 统一 shell 代码块中的说明文字引导符号为 #,与 shell 注释语法保持一致。
    • 修正一处代码块语法,从 bash 改为 ini
  • 项目 邮箱登录报错啊

    项目 邮箱登录报错啊

    
    runtime error: invalid memory address or nil pointer dereference
    D:/Program Files/Go/src/runtime/panic.go:221 (0xeaa704)
    	panicmem: panic(memoryError)
    D:/Program Files/Go/src/runtime/signal_windows.go:254 (0xec14f0)
    	sigpanic: panicmem()
    d:/projects/eagle/web/response.go:51 (0x1d506d2)
    	SetLoginCookie: session.Options = &sessions.Options{
    d:/projects/eagle/web/user/login.go:55 (0x1d51744)
    	DoLogin: web.SetLoginCookie(c, d.ID)
    C:/Users/Administrator/go/pkg/mod/github.com/gin-gonic/[email protected]/context.go:165 (0x15e451c)
    	(*Context).Next: c.handlers[c.index](c)
    d:/projects/eagle/internal/middleware/translations.go:37 (0x1d1bcb5)
    	Translations.func1: c.Next()
    C:/Users/Administrator/go/pkg/mod/github.com/gin-gonic/[email protected]/context.go:165 (0x15e451c)
    	(*Context).Next: c.handlers[c.index](c)
    d:/projects/eagle/pkg/middleware/timeout.go:34 (0x1d40b84)
    	Timeout.func1: c.Next()
    C:/Users/Administrator/go/pkg/mod/github.com/gin-gonic/[email protected]/context.go:165 (0x15e451c)
    	(*Context).Next: c.handlers[c.index](c)
    d:/projects/eagle/pkg/middleware/tracing.go:91 (0x1d418f5)
    	Tracing.func1: c.Next()
    C:/Users/Administrator/go/pkg/mod/github.com/gin-gonic/[email protected]/context.go:165 (0x15e451c)
    	(*Context).Next: c.handlers[c.index](c)
    d:/projects/eagle/pkg/middleware/metrics.go:93 (0x1d3ffd2)
    	Metrics.func1: c.Next()
    C:/Users/Administrator/go/pkg/mod/github.com/gin-gonic/[email protected]/context.go:165 (0x15e451c)
    	(*Context).Next: c.handlers[c.index](c)
    d:/projects/eagle/pkg/middleware/requestid.go:33 (0x1d4292e)
    	RequestID.func1: c.Next()
    C:/Users/Administrator/go/pkg/mod/github.com/gin-gonic/[email protected]/context.go:165 (0x15e451c)
    	(*Context).Next: c.handlers[c.index](c)
    d:/projects/eagle/pkg/middleware/logging.go:60 (0x1d42213)
    	Logging.func1: c.Next()
    C:/Users/Administrator/go/pkg/mod/github.com/gin-gonic/[email protected]/context.go:165 (0x15e451c)
    	(*Context).Next: c.handlers[c.index](c)
    d:/projects/eagle/pkg/middleware/header.go:24 (0x1d3f7ac)
    	Options: c.Next()
    C:/Users/Administrator/go/pkg/mod/github.com/gin-gonic/[email protected]/context.go:165 (0x15e451c)
    	(*Context).Next: c.handlers[c.index](c)
    d:/projects/eagle/pkg/middleware/header.go:16 (0x1d3f62e)
    	NoCache: c.Next()
    C:/Users/Administrator/go/pkg/mod/github.com/gin-gonic/[email protected]/context.go:165 (0x15e451c)
    	(*Context).Next: c.handlers[c.index](c)
    C:/Users/Administrator/go/pkg/mod/github.com/gin-gonic/[email protected]/recovery.go:99 (0x15f4b04)
    	CustomRecoveryWithWriter.func1: c.Next()
    C:/Users/Administrator/go/pkg/mod/github.com/gin-gonic/[email protected]/context.go:165 (0x15e451c)
    	(*Context).Next: c.handlers[c.index](c)
    C:/Users/Administrator/go/pkg/mod/github.com/gin-gonic/[email protected]/gin.go:489 (0x15f2913)
    	(*Engine).handleHTTPRequest: c.Next()
    C:/Users/Administrator/go/pkg/mod/github.com/gin-gonic/[email protected]/gin.go:445 (0x15f241a)
    	(*Engine).ServeHTTP: engine.handleHTTPRequest(c)
    D:/Program Files/Go/src/net/http/server.go:2878 (0x12a16f9)
    	serverHandler.ServeHTTP: handler.ServeHTTP(rw, req)
    D:/Program Files/Go/src/net/http/server.go:1929 (0x129b1b7)
    	(*conn).serve: serverHandler{c.server}.ServeHTTP(w, w.req)
    D:/Program Files/Go/src/runtime/asm_amd64.s:1581 (0xedeca0)
    	goexit: BYTE	$0x90	// NOP
    
  • make build报undefined: unsafe

    make build报undefined: unsafe

    go 版本1.16.4 系统 windows make build 后:

    D:\web\goiot\eagle>make build
    FIND: 参数格式不正确
    github.com/go-eagle/eagle/pkg/utils
    # github.com/go-eagle/eagle/pkg/utils
    pkg\utils\string.go:75:20: undefined: unsafe
    pkg\utils\string.go:80:23: undefined: unsafe
    pkg\utils\string.go:82:22: undefined: unsafe
    make: *** [Makefile:30: build] Error 2
    

    注释掉unsafe中的函数就正常了,请问注释掉影响大吗

  • 配置文件中的ReadTimeout,WriteTimeout没有用起来

    配置文件中的ReadTimeout,WriteTimeout没有用起来

    transport下的http.Server中的timeout没有跟标准包中的http.Server的ReadTimeout,WriteTimeout结合起来 https://github.com/1024casts/snake/blob/9c75dc4e686c308ad739aa1229acafeaff03ba3d/config/config.yaml#L16

  • fix: 修复低版本 jwt-go 存在安全漏洞的问题

    fix: 修复低版本 jwt-go 存在安全漏洞的问题

    jwt-go 4.0.0-preview1 版本之前存在安全漏洞,能够绕过访问限制,本次提交修复了这个问题。

    FYI:

    • https://www.cvedetails.com/cve/CVE-2020-26160/
    • https://github.com/dgrijalva/jwt-go/pull/426
  • Should Depend On sqlite3 v1.14.15 Instead of v2.01

    Should Depend On sqlite3 v1.14.15 Instead of v2.01

    Describe the bug

    $ go mod tidy
    go: github.com/go-eagle/[email protected] requires
            github.com/mattn/[email protected]+incompatible: reading github.com/mattn/go-sqlite3/go.mod at revision v2.0.1: unknown revision v2.0.1
    

    Sqlite3 documentation states that

    Latest stable version is v1.14 or later, not v2.

    And also there is no such version as v2.0.1 on github

  • 请问 log 不支持 Debug 级别是出于什么考虑呢?

    请问 log 不支持 Debug 级别是出于什么考虑呢?

    你好,我看之前的代码提交记录,在最初 log.Logger 是支持 Debug 级别的日志记录的,后来在 chore: optimize log 这次提交中去掉了 Debug 级别的支持。

    我理解 Debug 级别的日志和 Info 级别的日志差别还是比较大的。在实践经验中,通常是开发和测试环境中开启 Debug 级别的日志,生产环境中开启 Info 日志,缺少 Debug 级别的日志会对测试环境排查问题有比较大的影响。

    想了解下咱们是出于什么原因选择了这样的设计,以及后续是否有计划重新增加对 Debug 级别日志的支持呢?

  • 起 docker 报错

    起 docker 报错

    2021/10/29 07:22:55 ParseConfig: 1 error(s) decoding:

    • cannot parse 'MySQL.ConnMaxLifeTime' as int: strconv.ParseInt: parsing "60m": invalid syntax