文档目录
glen
· 预计阅读 3 分钟

第 1 章 第一个 Owl 应用:5 分钟跑通

本文导航
第 1 章 第一个 Owl 应用:5 分钟跑通

第 1 章 第一个 Owl 应用:5 分钟跑通

这一章只做一件事:用最少的代码把 Owl 服务启动起来,并成功访问一个接口。先建立「它真的能跑」的信心,再去理解内部机制。

环境准备

  • Go 版本以 owl/go.mod 为准(当前声明 go 1.26.0);
  • 新建一个空目录,例如 my-order;
  • 本地已有 owl/ 仓库。

1. 初始化模块

mkdir my-order && cd my-order
go mod init my-order

在 go.mod 里引用本地框架:

module my-order

go 1.26.0

require bit-labs.cn/owl v0.0.0

replace bit-labs.cn/owl => ../owl

replace 只用于本地联调。发布时改用正式版本号,并删掉 replace。

2. 写入口

// main.go
package main

import (
	"bit-labs.cn/owl"
	"my-order/app"
)

func main() {
	owl.NewApp(&app.SubAppOrder{}).WebShell()
}

NewApp 收集子应用,WebShell 负责装配并启动 HTTP 服务。命令行模式则用 ConsoleShell,见第 2 章。

3. 写子应用

// app/app.go
package app

import (
	"bit-labs.cn/owl"
	"bit-labs.cn/owl/contract/foundation"
	"bit-labs.cn/owl/provider/db"
	"bit-labs.cn/owl/provider/router"
	"github.com/gin-gonic/gin"
	"github.com/spf13/cobra"
	"gorm.io/gorm"
)

var _ owl.SubApp = (*SubAppOrder)(nil)

type SubAppOrder struct {
	// 字段名必须是 app,框架用反射注入
	app foundation.Application
}

func (i *SubAppOrder) Name() string { return "order" }

func (i *SubAppOrder) ServiceProviders() []foundation.ServiceProvider {
	return []foundation.ServiceProvider{
		&router.RouterServiceProvider{},
		&db.DBServiceProvider{},
	}
}

func (i *SubAppOrder) RegisterRouters() {
	// 本章先用最直接的方式注册一条路由
	err := i.app.Invoke(func(engine *gin.Engine) {
		engine.GET("/api/v1/ping", func(c *gin.Context) {
			c.JSON(200, gin.H{"success": true, "msg": "pong"})
		})
	})
	owl.PanicIf(err)
}

func (i *SubAppOrder) Binds() []any                    { return nil }
func (i *SubAppOrder) Bootstrap()                      {}
func (i *SubAppOrder) RegisterMenus() []*router.Menu   { return nil }
func (i *SubAppOrder) RegisterCommands() []*cobra.Command { return nil }
func (i *SubAppOrder) RegisterMigrate() []any          { return nil }
func (i *SubAppOrder) BeforeMigrate(*gorm.DB) error    { return nil }
func (i *SubAppOrder) AfterMigrate(*gorm.DB) error     { return nil }

补上 gin 的 import(github.com/gin-gonic/gin)。

这里有三个必须注意的点:

  1. SubAppOrder 必须有名为 app 的字段,否则框架反射注入时会 panic。
  2. ServiceProviders() 至少要有 router.RouterServiceProvider,否则拿不到 *gin.Engine。
  3. Binds() 返回的是构造函数列表,不是实例;本章没有业务服务,所以返回 nil。

4. 拉依赖并启动

go mod tidy
go run .

第一次启动时,框架会:

  • 创建 conf/ 目录;
  • 根据各 Provider 的 Conf() 生成 conf/router.yaml、conf/database.yaml、conf/log.yaml 等;
  • 读取配置、初始化日志、注册路由;
  • 在 conf/router.yaml 的 server.host:port(默认 0.0.0.0:8080)上监听。

看到类似下面的日志就说明服务起来了:

启动 HTTP 服务器 监听 0.0.0.0:8080

5. 验证

curl "http://127.0.0.1:8080/health"
curl "http://127.0.0.1:8080/api/v1/ping"

期望分别返回 {"status":"ok"} 和 {"success":true,"msg":"pong"}。

6. 先不用数据库

本章注册了 db.DBServiceProvider,但没有配置数据库。默认 conf/database.yaml 指向 PostgreSQL,如果本机没有数据库,启动阶段可能报连接错误。

最省事的做法是先用 SQLite:

# conf/database.yaml
driver: sqlite
host: my-order.db
database: main

SQLite 模式下,数据库文件会放在 conf/ 目录下(框架把 host 拼到配置目录)。

如果你暂时只想验证 HTTP,也可以先移除 db.DBServiceProvider,下一章再按需加回。

7. 这一章你完成了什么

  • 建立了 main.go + app/ 的最小结构;
  • 让框架自动生成了配置;
  • 启动了一个真实的 Gin 服务;
  • 用一条路由验证了 HTTP 链路。

下一章会把这条链路拆开,说明从 NewApp 到 WebShell 每一步的执行顺序,以及为什么配置必须先落盘、再注册 Provider。

自检

  • [ ] 服务能启动且不 panic;
  • [ ] conf/ 目录已自动生成;
  • [ ] /health 与 /api/v1/ping 都能访问;
  • [ ] 能说出 app 字段和 ServiceProviders() 各自的作用。

评论

加载评论中…

暂无上一篇
下一篇 第 2 章 应用生命周期:从 NewApp 到 WebShell

BitLabs © 2026 · 论坛讨论 · 博客长文 · 文档手册