基于 Golang AST 自动生成建表 sql

写后台业务的同学经常调侃自己的工作就是围绕数据表CRUD. 虽然实际工作并不会如此简单,但是日常中的确有很多类似的重复、缺乏创造性的工作。而这种工作上是可以在一定程度上自动化的。为了提供业务研发人员开发效率,前段时间我们开发了一个后端开发工作流工具,主要提供以下功能:

  • 生成服务器API基础代码以及Swagger文档注释 (只支持gin框架)
  • 生成服务器API客户端代码
  • go struct 批量添加 tag
  • 生成 gorm model struct
  • model struct 生成 sql

因为这些功能跟我们内部的公共库有一定耦合,因此整个工具可能无法开源出来。这里,我们以model struct 生成 sql功能为例,聊聊我们在做这个工具的思路和使用到的工具。

任务

这里以我们在项目中使用的jinzhu同学的gorm作为orm库。如果你在使用golang的其他orm lib,实现方式应该大同小异。

我们的任务是从下面的这个model struct定义:

生成 mysql 建表语句(文件):

思路

model struct 生成 sql是一个将语言A翻译为语言B的问题。而这个过程跟我们平时将源代码编译为二进制可执行程序从原理上说是没有区别的。因此,这个问题本质上是一个编译问题。一个完整的编译包含以下步骤:

对于本文要完成的任务来说,主要完成词法分析、语法分析、目标代码生成即可。

工具

要完成词法分析和语法分析,我们有上古神器 LexYacc, Yet Another Compiler-Compiler. 而我们只是想完成一个建表文件的生成任务而已,使用者两个工具有时候要自定义语法,又是要自己写lex和yacc文件,累觉不爱……

Golang 有很多其他语言羡慕不来的工具,例如 go pprof, go list, go vet 等。在语言元编程方面,go 1.4实现了自举;而编译时候涉及到的词法分析和语法分析很早前就放在了标准库 go/ast 中。AST是abstract syntax tree的缩写,直译过来是抽象语法树。通过AST,我们可以编写一个go程序解析go源代码。具体到本文要完成的任务,要编写一个这样的程序解析定义数据表的model struct, 然后生成sql建表语句。

实现

具体到我们的任务实现,可以拆分为如下几个步骤:

  • 加载源代码,生成 AST Tree
  • 获取和解析 model struct AST
  • 根据struct field name/tag 生成create_definition, table_options

完整代码实现,可以移步github gorm2sql.

实现效果:

user_email.go:

type UserBase struct {
    UserId string `sql:"index:idx_ub"`
    Ip     string `sql:"unique_index:uniq_ip"`
}

type UserEmail struct {
    Id       int64    `gorm:"primary_key"`
    UserBase
    Email      string
    Sex        bool
    Age        int
    Score      float64
    UpdateTime time.Time `sql:"default:CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"`
    CreateTime time.Time `sql:"default:CURRENT_TIMESTAMP"`
}
gorm2sql sql -f user_email.go -s UserEmail -o db.sql

Result:

CREATE TABLE `user_email`
(
  `id` bigint AUTO_INCREMENT NOT NULL ,
  `user_id` varchar(128) NOT NULL ,
  `ip` varchar(128) NOT NULL ,
  `email` varchar(128) NOT NULL ,
  `sex` boolean NOT NULL ,
  `age` int NOT NULL ,
  `score` double NOT NULL ,
  `update_time` datetime NOT NULL  DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `create_time` datetime NOT NULL  DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_ub (`user_id`),
  UNIQUE INDEX uniq_ip (`ip`),
  PRIMARY KEY (`id`)
) engine=innodb DEFAULT charset=utf8mb4;

扩展阅读

Yet Another GeoIP Alfred Workflow

Yet Another GeoIP Alfred Workflow

在浏览网站以及在选线路的时候,经常会习惯性的查看一下对方的IP及地理位置信息. 师兄的 ip.cn 以及 ipip.net 都是不错的选择。但无奈自己是小帽子 Alfred 控,本着少做体力活的原则,写了一个 Workflow.

ip me 查看本地的外网ip及位置信息

ip domain 查看域名ip及位置信息

ip url 查看url中域名ip及位置信息

如果觉得你也有这样需求,可以在这里下载,enjoy!

References

  • http://www.deanishe.net/alfred-workflow

GitLab搭建笔记

GitLab搭建笔记

GitLab使用Omnibus package搭建是分分钟就可以搞定的事情,但是我搭建的GitLab服务:

  1. 服务器在墙内;
  2. 服务器在一个路由器后面;

因此,浪费了一个晚上时间才把几分钟计划完成的事情搞定。这里给遇到类似情况的程序员做个简单的笔记,希望可以为他们节约一点时间吧。

服务器在墙内

Omnibus package(大小约340MB)通过AWS S3(AWS Region为美国新泽西)进行分发。墙内下载该package的速度极慢(电信百兆宽带下载速度约8KB/S),且部分时间段packages-gitlab-com.s3.amazonaws.com域名疑似被污染(四川成都地区)。因此,只好通过墙外的VPS中转下载。当前最新版(2015.08.30)为gitlab-ce_7.14.1-ce.0_amd64.deb. 上传了一份到百度网盘, 密码: qf38。阅兵在即,墙外要中转一个文件进来真是浪费生命,唉…

服务器在路由器后

这个比较好解决,需要映射两个端口。

  1. 映射Web端口: 映射路由器外部GitLab端口到GitLab配置端口(/etc/gitlab/gitlab.rb external_url ‘http://your-host:PORT’ );
  2. 映射SSH端口: 映射路由器外部GitLab SSH端口到22端口, 然后修改/etc/gitlab/gitlab.rb文件:
    • gitlab_rails[‘gitlab_shell_ssh_port’] = 路由器外部GitLab SSH端口
    • 注意:gitlab_rails[‘gitlab_shell_ssh_port’]只是配置代码库地址的端口,不会修改SSH真实使用的端口。
  3. sudo gitlab-ctl reconfigure