fix(仓储): 修复 ApiKey 更新并发语义

ApiKey 更新时显式设置 updated_at 并回填,避免二次查询竞态
补充软删除范围注释以统一审计语义
This commit is contained in:
yangjianbo
2025-12-29 19:59:36 +08:00
parent 74db0c15ae
commit 042d82359c
2 changed files with 22 additions and 17 deletions

View File

@@ -91,32 +91,35 @@ func (r *apiKeyRepository) GetByKey(ctx context.Context, key string) (*service.A
}
func (r *apiKeyRepository) Update(ctx context.Context, key *service.ApiKey) error {
exists, err := r.activeQuery().Where(apikey.IDEQ(key.ID)).Exist(ctx)
if err != nil {
return err
}
if !exists {
return service.ErrApiKeyNotFound
}
builder := r.client.ApiKey.UpdateOneID(key.ID).
// 使用原子操作:将软删除检查与更新合并到同一语句,避免竞态条件。
// 之前的实现先检查 Exist 再 UpdateOneID若在两步之间发生软删除
// 则会更新已删除的记录。
// 这里选择 Update().Where(),确保只有未软删除记录能被更新。
// 同时显式设置 updated_at避免二次查询带来的并发可见性问题。
now := time.Now()
builder := r.client.ApiKey.Update().
Where(apikey.IDEQ(key.ID), apikey.DeletedAtIsNil()).
SetName(key.Name).
SetStatus(key.Status)
SetStatus(key.Status).
SetUpdatedAt(now)
if key.GroupID != nil {
builder.SetGroupID(*key.GroupID)
} else {
builder.ClearGroupID()
}
updated, err := builder.Save(ctx)
if err == nil {
key.UpdatedAt = updated.UpdatedAt
return nil
affected, err := builder.Save(ctx)
if err != nil {
return err
}
if dbent.IsNotFound(err) {
if affected == 0 {
// 更新影响行数为 0说明记录不存在或已被软删除。
return service.ErrApiKeyNotFound
}
return err
// 使用同一时间戳回填,避免并发删除导致二次查询失败。
key.UpdatedAt = now
return nil
}
func (r *apiKeyRepository) Delete(ctx context.Context, id int64) error {

View File

@@ -289,8 +289,10 @@ func (r *groupRepository) DeleteCascade(ctx context.Context, id int64) ([]int64,
}
// 2. Clear group_id for api keys bound to this group.
// 仅更新未软删除的记录,避免修改已删除数据,保证审计与历史回溯一致性。
// 与 ApiKeyRepository 的软删除语义保持一致,减少跨模块行为差异。
if _, err := txClient.ApiKey.Update().
Where(apikey.GroupIDEQ(id)).
Where(apikey.GroupIDEQ(id), apikey.DeletedAtIsNil()).
ClearGroupID().
Save(ctx); err != nil {
return nil, err