2. Ent ORM 基础:Ent 简介、Schema 定义、代码生成、CRUD 操作
2.1 Ent 简介:面向支付的实体框架
Ent 是 Facebook 开源的一款 Go 语言实体框架(Entity Framework),专为复杂数据模型与高并发场景设计。在支付中间件工程中,Ent 的核心价值体现在以下三个维度:
- 静态类型与代码生成:通过
entc编译器从 Schema 定义生成强类型代码,杜绝运行时反射与类型断言,编译期即可捕获字段名拼写、关联关系错误。 - 图遍历与关联查询:支付领域天然存在订单→支付单→退款单→对账记录的网状关联,Ent 的
Eager Loading与Edge Traversal可一次查询完成多层嵌套加载。 - 事务与钩子(Hooks):Ent 提供
Tx事务闭包与生命周期钩子,适合在支付核心链路中嵌入审计、幂等、分库分表逻辑。
选型建议:若团队已采用 gRPC + Protobuf 微服务体系,Ent 的 Schema 与 Protobuf 可共用字段约束,减少模型层重复定义。支付场景下优先选择 Ent 而非 GORM,因为 Ent 的代码生成能提前暴露字段变更风险。
2.2 Schema 定义:支付核心模型
支付中间件中最核心的实体是 支付订单(PaymentOrder) 与 交易流水(Transaction)。以下 Schema 定义展示了字段、索引、唯一约束与关联边:
// ent/schema/payment_order.go
package schema
import (
"entgo.io/ent"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/index"
"entgo.io/ent/schema/edge"
)
type PaymentOrder struct {
ent.Schema
}
func (PaymentOrder) Fields() []ent.Field {
return []ent.Field{
field.String("order_no").MaxLen(32).Unique().
Comment("业务订单号,全局唯一"),
field.Int64("merchant_id").Comment("商户ID"),
field.Int64("amount").Comment("支付金额,单位分"),
field.Int8("status").Default(0).
Comment("0-待支付 1-支付中 2-成功 3-失败 4-退款中"),
field.Time("created_at"),
field.Time("updated_at"),
}
}
func (PaymentOrder) Indexes() []ent.Index {
return []ent.Index{
index.Fields("merchant_id", "status"),
index.Fields("order_no").Unique(),
}
}
func (PaymentOrder) Edges() []ent.Edge {
return []ent.Edge{
edge.To("transactions", Transaction.Type),
}
}
// ent/schema/transaction.go
package schema
import (
"entgo.io/ent"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/edge"
)
type Transaction struct {
ent.Schema
}
func (Transaction) Fields() []ent.Field {
return []ent.Field{
field.String("trans_no").MaxLen(64).Unique().
Comment("支付渠道流水号"),
field.Int64("payment_order_id").Comment("关联支付订单ID"),
field.Int64("channel_fee").Default(0).
Comment("渠道手续费,单位分"),
field.String("channel").MaxLen(16).
Comment("渠道:wechat/alipay/unionpay"),
field.Time("trans_time"),
}
}
func (Transaction) Edges() []ent.Edge {
return []ent.Edge{
edge.From("payment_order", PaymentOrder.Type).
Ref("transactions").
Unique().Field("payment_order_id").
Required(),
}
}
| 字段/约束 | 支付场景说明 | Ent 实现方式 |
|---|---|---|
order_no 唯一索引 |
防止重复下单,幂等拦截 | field.String().Unique() |
merchant_id + status 联合索引 |
商户后台按状态查询订单列表 | index.Fields("merchant_id", "status") |
payment_order_id 外键 |
支付订单→交易流水 1:N 关联 | edge.From().Ref().Field().Required() |
amount 单位分 |
避免浮点数精度丢失 | field.Int64() |
支付字段避坑:金额字段禁止使用
float64 或 field.Float(),必须使用 int64 以分为单位存储。渠道流水号 trans_no 必须设置唯一索引,用于对账时的幂等匹配。
2.3 代码生成:entc 工作流
Ent 通过 entc 命令行工具从 Schema 目录生成实体代码、客户端 API 与 SQL 迁移文件。标准生成流程如下:
- 初始化项目:
go run entgo.io/ent/cmd/entc init PaymentOrder Transaction - 编写 Schema:在
ent/schema/目录下定义字段、索引、边。 - 执行生成:
go generate ./ent或entc generate ./ent/schema - 检查产出:生成
ent/client.go、ent/paymentorder.go、ent/transaction.go等文件。
生成产物清单:
ent/client.go— 数据库客户端,包含事务、批量操作入口ent/paymentorder_create.go— 创建订单的链式构造器ent/paymentorder_query.go— 查询订单的 Where/Order/Limit 方法ent/migrate/— 自动生成的 SQL 迁移文件(支持 MySQL/PostgreSQL/SQLite)
2.4 CRUD 操作:支付订单生命周期
以下代码演示了使用 Ent 客户端完成支付订单的创建、查询、更新与删除操作。所有操作均基于生成的强类型 API。
2.4.1 创建支付订单
func CreatePaymentOrder(ctx context.Context, client *ent.Client) (*ent.PaymentOrder, error) {
return client.PaymentOrder.
Create().
SetOrderNo("PO20250321001").
SetMerchantID(10086).
SetAmount(2999). // 29.99 元
SetStatus(0). // 待支付
SetCreatedAt(time.Now()).
SetUpdatedAt(time.Now()).
Save(ctx)
}
2.4.2 查询订单并加载关联流水
func QueryOrderWithTransactions(ctx context.Context, client *ent.Client, orderNo string) (*ent.PaymentOrder, error) {
return client.PaymentOrder.
Query().
Where(paymentorder.OrderNoEQ(orderNo)).
WithTransactions(func(q *ent.TransactionQuery) {
q.Order(ent.Asc(transaction.FieldTransTime))
}).
Only(ctx)
}
2.4.3 更新订单状态(支付成功回调)
func UpdateOrderStatus(ctx context.Context, client *ent.Client, orderID int, newStatus int8) error {
return client.PaymentOrder.
UpdateOneID(orderID).
SetStatus(newStatus).
SetUpdatedAt(time.Now()).
Exec(ctx)
}
2.4.4 事务内创建订单与流水
func CreateOrderWithTransaction(ctx context.Context, client *ent.Client) error {
tx, err := client.Tx(ctx)
if err != nil {
return err
}
defer tx.Rollback() // 出错时回滚
order, err := tx.PaymentOrder.
Create().
SetOrderNo("PO20250321002").
SetMerchantID(10086).
SetAmount(5000).
SetStatus(1). // 支付中
SetCreatedAt(time.Now()).
SetUpdatedAt(time.Now()).
Save(ctx)
if err != nil {
return err
}
_, err = tx.Transaction.
Create().
SetTransNo("TXN202503210001").
SetPaymentOrderID(order.ID).
SetChannel("wechat").
SetChannelFee(30).
SetTransTime(time.Now()).
Save(ctx)
if err != nil {
return err
}
return tx.Commit()
}
| 操作 | Ent 方法 | 支付场景示例 |
|---|---|---|
| 创建 | client.PaymentOrder.Create().SetXxx().Save(ctx) |
用户发起支付,生成待支付订单 |
| 查询 | .Query().Where().WithTransactions().Only(ctx) |
查询订单详情并加载所有交易流水 |
| 更新 | .UpdateOneID(id).SetStatus().Exec(ctx) |
支付回调更新订单状态为成功 |
| 删除 | .DeleteOneID(id).Exec(ctx) |
超时未支付订单软删除(建议用状态标记) |
| 事务 | client.Tx(ctx) + tx.Commit() |
创建订单 + 流水必须原子提交 |
支付事务要点:支付核心链路中,订单创建与流水写入必须在同一个数据库事务内完成。Ent 的
Tx 对象与普通 Client 接口完全一致,只需将 client 替换为 tx 即可。事务提交前务必调用 defer tx.Rollback() 防止异常泄漏连接。
2.5 本章小结
- Ent 通过 Schema 定义 + 代码生成提供强类型 ORM,适合支付场景的高可靠性要求。
- 支付核心模型包括
PaymentOrder与Transaction,通过 Edge 建立 1:N 关联。 - 代码生成流程为:Schema → entc generate → 客户端 + 迁移文件 + 实体 CRUD。
- CRUD 操作全部基于生成的链式 API,事务支持保证订单与流水的原子写入。