Skip to content

Phase 6: 示例应用与集成 ​

目标:创建完整的示例应用,验证框架各模块的集成

1. 示例项目结构 ​

_test/
└── demo/
    ├── main.go                    # 应用入口
    ├── generated_test.go          # 生成代码集成测试
    └── internal/
        └── user/                  # Generator 生成的 user 表代码
            ├── base.go            # BaseUser + Table + getter/setter (覆盖写入)
            ├── user.go            # User struct (存在则跳过)
            ├── dao.go             # Dao + FindById/FindBy 等 (存在则跳过)
            └── service.go         # Service + HTTP 路由 (存在则跳过)

2. 完整示例应用(基于实际 demo) ​

go
package main

import (
    "fmt"

    "github.com/crazy-airhead/aifei-go"
    "github.com/crazy-airhead/aifei-go/db"
    "github.com/crazy-airhead/aifei-go/server"

    // 生成的 per-table 包:通过 init() 注册 Table 元数据和 Service 路由
    _ "github.com/crazy-airhead/aifei-go/_test/demo/internal/user"

    _ "modernc.org/sqlite"
)

func main() {
    // 初始化数据库
    err := db.Init("sqlite", "./demo.db", db.WithPrinter(func(sql string, args ...interface{}) {
        fmt.Printf("[SQL] %s %v\n", sql, args)
    }))
    if err != nil {
        panic(err)
    }

    // 确保表存在
    db.SQL(`CREATE TABLE IF NOT EXISTS user (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        age INTEGER DEFAULT 0,
        email TEXT,
        created_at TEXT DEFAULT CURRENT_TIMESTAMP
    )`).Update()

    app := aifei.New()

    // 全局 Handler 包装链
    app.Use(server.Logger(), server.Recover())

    // 路由 — HandlerFunc: func(in aifei.Input) aifei.Output
    app.GET("/", func(in aifei.Input) aifei.Output {
        return server.OkMsg("Aifei Go " + aifei.Version)
    })

    // 自动注册所有 init() 注册的服务
    server.AutoRegisterServices(app)

    // 自定义路由组
    admin := app.Group("/api/admin")
    admin.GET("/dashboard", func(in aifei.Input) aifei.Output {
        return server.OkMsg("admin dashboard")
    })

    // 启动
    server.Run(app, ":8080", server.WithCORS("*"))
}

3. Service 示例(使用生成代码 + 拦截器) ​

go
// internal/user/service.go
package user

import (
    "strconv"

    "github.com/crazy-airhead/aifei-go"
    "github.com/crazy-airhead/aifei-go/db"
    "github.com/crazy-airhead/aifei-go/server"
)

const ServicePrefix = "/user"

func init() {
    server.RegisterService(ServicePrefix, &Service{})
}

// MethodInterceptors 声明方法级拦截器
func (s *Service) MethodInterceptors() map[string][]aifei.Interceptor {
    return map[string][]aifei.Interceptor{
        "Create": {server.TxInterceptor()},
    }
}

type Service struct{}

// List 分页列表 — GET /user/list
func (s *Service) List(in aifei.Input) aifei.Output {
    pageNum := in.GetInt("page", 1)
    pageSize := in.GetInt("size", 10)
    page, err := db.SQL("SELECT * FROM user ORDER BY id DESC").Paginate(pageNum, pageSize)
    if err != nil {
        return server.Fail(err.Error())
    }
    return server.Of(page)
}

// Create 创建 — POST /user/create
func (s *Service) Create(in aifei.Input) aifei.Output {
    u := New()
    if err := in.GetBean(u); err != nil {
        return server.Fail("invalid request: " + err.Error())
    }
    result, err := u.Insert()
    if err != nil {
        return server.Fail(err.Error())
    }
    return server.Of(result.GetID())
}

// GetById 主键查询 — GET /user/:id
func (s *Service) GetById(in aifei.Input) aifei.Output {
    id, err := strconv.Atoi(in.Param("id"))
    if err != nil {
        return server.Fail("invalid id")
    }
    u, err := FindById(id)
    if err != nil {
        return server.Fail(err.Error())
    }
    if u == nil {
        return server.Fail("User not found")
    }
    return server.Of(u)
}

// UpdateById 更新 — PUT /user/:id
func (s *Service) UpdateById(in aifei.Input) aifei.Output {
    id, _ := strconv.Atoi(in.Param("id"))
    existing, _ := FindById(id)
    if existing == nil {
        return server.Fail("User not found")
    }
    if err := in.GetBean(existing); err != nil {
        return server.Fail("invalid request")
    }
    if _, err := existing.Update(); err != nil {
        return server.Fail(err.Error())
    }
    return server.Of(id)
}

// DeleteById 删除 — DELETE /user/:id
func (s *Service) DeleteById(in aifei.Input) aifei.Output {
    id, _ := strconv.Atoi(in.Param("id"))
    if _, err := DeleteById(id); err != nil {
        return server.Fail(err.Error())
    }
    return server.Of(nil)
}

4. 事务示例 ​

go
func Transfer(in aifei.Input) aifei.Output {
    from := in.GetInt64("from")
    to := in.GetInt64("to")
    money := in.GetInt("money")

    err := db.Transaction(func() error {
        n1, err := db.SQL("UPDATE account SET balance = balance - ? WHERE id = ?", money, from).Update()
        if err != nil {
            return err
        }
        n2, err := db.SQL("UPDATE account SET balance = balance + ? WHERE id = ?", money, to).Update()
        if err != nil {
            return err
        }
        if n1 != 1 || n2 != 1 {
            return fmt.Errorf("transfer failed")
        }
        return nil
    })

    if err != nil {
        return server.Fail(err.Error())
    }
    return server.OkMsg("success")
}

5. 批量操作示例 ​

go
func BatchInsert(in aifei.Input) aifei.Output {
    batch := db.NewBatch()
    rows := make([]*db.Row, 0, 100)
    for i := 0; i < 100; i++ {
        row := db.NewRow("user").
            Set("name", fmt.Sprintf("user_%d", i)).
            Set("age", 18+i%50)
        rows = append(rows, row)
    }

    result, err := batch.Insert(rows)
    if err != nil {
        return server.Fail(err.Error())
    }
    return server.OkMsg(fmt.Sprintf("inserted %d rows", result.RowsAffected))
}

6. 响应格式约定 ​

使用 server.Out 流畅构建器生成统一响应格式:

go
// 成功响应
server.Ok()              // {"code": 0, "msg": "ok"}
server.OkMsg("success")  // {"code": 0, "msg": "success"}
server.Of(data)          // {"code": 0, "msg": "ok", "data": data}
server.OfField("user", u) // {"code": 0, "msg": "ok", "data": {"user": {...}}}

// 失败响应
server.Fail("error")       // {"code": 90000, "msg": "error"}
server.FailWithCode(40001, "invalid") // {"code": 40001, "msg": "invalid"}

// Out 常量
server.CodeOK   = 0
server.CodeFail = 90000

7. go.mod 依赖 ​

module github.com/crazy-airhead/aifei-go

go 1.27

// 零第三方依赖!仅使用 Go 标准库
// database/sql, encoding/json, net/http, log, etc.
// 用户按需引入数据库驱动:
//   _ "github.com/go-sql-driver/mysql"
//   _ "github.com/lib/pq"
//   _ "modernc.org/sqlite"

8. 测试策略 ​

已有测试覆盖:

测试文件行数覆盖范围
db/db_test.go53基础 DB 操作
db/sql/sql_test.go228SqlKit、SqlPara、SQL 指令
enjoy/enjoy_test.go182模板引擎渲染
json/json_test.go45Marshal/Unmarshal
log/log_test.go114日志级别和输出
generator/generator_test.go314代码生成器和模板
_test/demo/generated_test.go152生成代码集成测试
_test/db_test/db_test.go971CRUD、分页、事务、批量、Enjoy SQL 全指令

集成测试示例(SQLite 内存数据库) ​

go
func setupTestDB(t *testing.T) {
    err := db.Init("sqlite", ":memory:")
    if err != nil {
        t.Fatal(err)
    }
    db.SQL(`CREATE TABLE user (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)`).Update()
}

func TestInsert(t *testing.T) {
    setupTestDB(t)
    row := db.NewRow("user").Set("name", "james").Set("age", 18)
    result, err := db.Insert(row)
    if err != nil {
        t.Fatal(err)
    }
    if result.GetInt("id") == 0 {
        t.Error("expected non-zero id after insert")
    }
}

func TestEnjoySQL(t *testing.T) {
    setupTestDB(t)
    db.Insert(db.NewRow("user").Set("name", "alice").Set("age", 25))

    // 测试 #where + #and + #para 动态条件
    rows, err := db.Sql(
        "select * from user #where() #and(age > #para(minAge))",
        map[string]interface{}{"minAge": 20},
    ).Find()

    if err != nil || len(rows) != 1 {
        t.Fatal("expected 1 row")
    }
}

基于 Apache-2.0 许可发布