From 5584709ac9da12c44cb3e09822b17be8aaec143f Mon Sep 17 00:00:00 2001 From: yangjianbo Date: Mon, 29 Dec 2025 15:49:20 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E4=BB=93=E5=82=A8=E5=B1=82):=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E4=BA=8B=E5=8A=A1=20ent=20client=20=E8=B0=83=E7=94=A8?= =?UTF-8?q?=20Close()=20=E5=AF=BC=E8=87=B4=E7=9A=84=20panic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题:创建用户时发生 panic,错误信息为 "interface conversion: sql.ExecQuerier is *sql.Tx, not *sql.DB" 原因:基于事务创建的 ent client 在调用 Close() 时,ent 的 sql driver 会尝试将 ExecQuerier 断言为 *sql.DB 来关闭连接,但实际类型是 *sql.Tx 修复:移除对 txClient.Close() 的调用,事务的清理通过 sqlTx.Rollback() 和 sqlTx.Commit() 完成即可 影响范围: - user_repo.go: Create 和 Update 方法 - group_repo.go: Delete 方法 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- backend/internal/repository/group_repo.go | 8 +++----- backend/internal/repository/user_repo.go | 16 ++++++---------- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/backend/internal/repository/group_repo.go b/backend/internal/repository/group_repo.go index 5b9563a2..f54588ce 100644 --- a/backend/internal/repository/group_repo.go +++ b/backend/internal/repository/group_repo.go @@ -239,7 +239,6 @@ func (r *groupRepository) DeleteCascade(ctx context.Context, id int64) ([]int64, exec := r.sql txClient := r.client var sqlTx *sql.Tx - var txClientClose func() error if r.begin != nil { sqlTx, err = r.begin.BeginTx(ctx, nil) @@ -248,12 +247,11 @@ func (r *groupRepository) DeleteCascade(ctx context.Context, id int64) ([]int64, } exec = sqlTx txClient = entClientFromSQLTx(sqlTx) - txClientClose = txClient.Close + // 注意:不能调用 txClient.Close(),因为基于事务的 ent client + // 在 Close() 时会尝试将 ExecQuerier 断言为 *sql.DB,但实际是 *sql.Tx + // 事务的清理通过 sqlTx.Rollback() 和 sqlTx.Commit() 完成 defer func() { _ = sqlTx.Rollback() }() } - if txClientClose != nil { - defer func() { _ = txClientClose() }() - } // Lock the group row to avoid concurrent writes while we cascade. var lockedID int64 diff --git a/backend/internal/repository/user_repo.go b/backend/internal/repository/user_repo.go index be8cfb56..c81225ff 100644 --- a/backend/internal/repository/user_repo.go +++ b/backend/internal/repository/user_repo.go @@ -40,7 +40,6 @@ func (r *userRepository) Create(ctx context.Context, userIn *service.User) error exec := r.sql txClient := r.client var sqlTx *sql.Tx - var txClientClose func() error if r.begin != nil { var err error @@ -50,12 +49,11 @@ func (r *userRepository) Create(ctx context.Context, userIn *service.User) error } exec = sqlTx txClient = entClientFromSQLTx(sqlTx) - txClientClose = txClient.Close + // 注意:不能调用 txClient.Close(),因为基于事务的 ent client + // 在 Close() 时会尝试将 ExecQuerier 断言为 *sql.DB,但实际是 *sql.Tx + // 事务的清理通过 sqlTx.Rollback() 和 sqlTx.Commit() 完成 defer func() { _ = sqlTx.Rollback() }() } - if txClientClose != nil { - defer func() { _ = txClientClose() }() - } created, err := txClient.User.Create(). SetEmail(userIn.Email). @@ -126,7 +124,6 @@ func (r *userRepository) Update(ctx context.Context, userIn *service.User) error exec := r.sql txClient := r.client var sqlTx *sql.Tx - var txClientClose func() error if r.begin != nil { var err error @@ -136,12 +133,11 @@ func (r *userRepository) Update(ctx context.Context, userIn *service.User) error } exec = sqlTx txClient = entClientFromSQLTx(sqlTx) - txClientClose = txClient.Close + // 注意:不能调用 txClient.Close(),因为基于事务的 ent client + // 在 Close() 时会尝试将 ExecQuerier 断言为 *sql.DB,但实际是 *sql.Tx + // 事务的清理通过 sqlTx.Rollback() 和 sqlTx.Commit() 完成 defer func() { _ = sqlTx.Rollback() }() } - if txClientClose != nil { - defer func() { _ = txClientClose() }() - } updated, err := txClient.User.UpdateOneID(userIn.ID). SetEmail(userIn.Email).