## 编写一个 API 应用 > API 应用其实也是一个 Console 应用,只是在 Command 中启动了一个 gin 服务器,并将配置好的路由传入服务器中执行而已。 首先我们使用 `mix` 命令创建一个 API 项目骨架: ~~~ mix api --name=hello ~~~ 然后我打开骨架 `commands/api.go` 的源码: - APICommand 命令的中启动了一个 gin 服务器 - 并且设置 logrus 为服务器的日志组件 - 还捕获信号,做了服务器的 Shutdown 处理 - 代码中 `api.RouteDefinitions` 定义了全部的路由配置,只需修改这个全局变量即可扩展其他接口 ~~~ func (t *APICommand) Main() { logger := globals.Logger() // server gin.SetMode(dotenv.Getenv("GIN_MODE").String(gin.ReleaseMode)) router := gin.New(api.RouteDefinitions...) srv := &http.Server{ Addr: flag.Match("a", "addr").String(":8080"), Handler: router, } // signal ch := make(chan os.Signal) signal.Notify(ch, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM) go func() { <-ch logger.Info("Server shutdown") ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { globals.Logger().Errorf("Server shutdown error: %s", err) } }() // logger router.Use(gin.LoggerWithFormatter(logger, func(params gin.LogFormatterParams) string { return fmt.Sprintf("%s|%s|%d|%s", params.Method, params.Path, params.StatusCode, params.ClientIP, ) })) // run welcome() logger.Info("Server start") if err := srv.ListenAndServe(); err != nil && !strings.Contains(err.Error(), "http: Server closed") { panic(err) } } ~~~ 因为骨架中已经处理了基本的常用逻辑,所以我们无需修改这个文件,只需在直接修改 `api.RouteDefinitions`,该全局变量在 `routes/api/all.go` 文件中: - 我们在路由配置中增加一个 `add_user` 的路由 ~~~ router.POST("users/add", middleware.CorsMiddleware(), func(ctx *gin.Context) { hello := controllers.AddUserController{} hello.Index(ctx) }, ) ~~~ - 然后创建一个 `controllers.AddUserController` 结构体,文件路径为 `controllers/user.go`: 代码中使用 gorm 在 users 表中插入了一个新记录 ~~~ package controllers import ( "github.com/gin-gonic/gin" "github.com/mix-go/mix-api-skeleton/globals" "github.com/mix-go/mix-api-skeleton/models" "net/http" "time" ) type AddUserController struct { } func (t *AddUserController) Index(c *gin.Context) { db := globals.DB() if err := db.Create(&models.User{ Name: c.Request.PostFormValue("name"), CreateAt: time.Now(), }).Error; err != nil { c.JSON(http.StatusOK, gin.H{ "status": http.StatusInternalServerError, "message": err.Error(), }) return } c.JSON(http.StatusOK, gin.H{ "status": http.StatusOK, "message": "ok", }) } ~~~ - 上面的代码中使用了 `models.User` 模型,该文件定义在 `models/users.go`: 结构体中的备注指定了字段关联的数据库字段名称,表名可自行增加前缀等 ~~~ package models import "time" type User struct { ID int `gorm:"primary_key"` Name string `gorm:"column:name"` CreateAt time.Time `gorm:"column:create_at"` } func (User) TableName() string { return "users" } ~~~ - 上面使用的 `globals.DB()` 都是骨架中定义好的全局方法,方法内部是采用 `mix-go/bean` 库的依赖注入容器获取的全局 GORM 实例,改实例的依赖配置在 `manifest/beans/db.go` 文件中: 文件中的依赖配置定义了使用 `gorm.Open` 实例化,`bean.SINGLETON` 定义了这个实例化后的对象是单例模式,`ConstructorArgs` 字段定义了实例化时传入的构造参数,这里传入的 `DATABASE_DSN` 是从环境变量中获取的,也就是说如果我们要修改连接信息,我们还需要到 `.env` 环境配置文件中修改。 ~~~ package beans import ( "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/mysql" "github.com/mix-go/bean" "github.com/mix-go/dotenv" ) func DB() { Beans = append(Beans, bean.BeanDefinition{ Name: "db", Reflect: bean.NewReflect(gorm.Open), Scope: bean.SINGLETON, ConstructorArgs: bean.ConstructorArgs{"mysql", dotenv.Getenv("DATABASE_DSN").String()}, }, ) } ~~~ - 使用 Goland 编译后,在命令行启动 `api` 服务器,当然你也可以在 Goland Run 里配置 Program arguments 直接编译执行。 ~~~ $ bin/go_build_main_go api ___ ______ ___ _ /__ ___ _____ ______ / __ `__ \/ /\ \/ /__ __ `/ __ \ / / / / / / / /\ \/ _ /_/ // /_/ / /_/ /_/ /_/_/ /_/\_\ \__, / \____/ /____/ Server Name: mix-api Listen Addr: :8080 System Name: darwin Go Version: 1.13.4 Framework Version: 1.0.9 time=2020-09-16 20:24:41.515 level=info msg=Server start file=api.go:58 ~~~ - `curl` 测试 ~~~ $ curl --location --request POST 'http://127.0.0.1:8080/users/add' --form 'name=liujian' {"message":"ok","status":200} ~~~