You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

AdonisJS中数据库级联删除时,如何清理关联的S3存储文件?

解决方案

方案一:数据库触发器 + 定时清理任务

  • 步骤1:创建中间表deleted_post_images,用来记录待删除的图片URL,字段包含s3_url和processed(标记是否已处理)。
  • 步骤2:在PostgreSQL中编写触发器函数,当post_images表的记录被删除(包括级联删除)时,自动把被删记录的s3_url插入中间表:
CREATE OR REPLACE FUNCTION log_deleted_post_image()
RETURNS TRIGGER AS $$
BEGIN
  INSERT INTO deleted_post_images (s3_url) VALUES (OLD.s3_url);
  RETURN OLD;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trigger_post_image_deleted
AFTER DELETE ON post_images
FOR EACH ROW EXECUTE FUNCTION log_deleted_post_image();
  • 步骤3:在AdonisJS中用@adonisjs/cron创建定时任务,定期查询deleted_post_images里未处理的记录,调用S3 SDK批量删除对应文件,之后标记这些记录为已处理或直接删除。

这个方案不用修改现有删除逻辑,不管是级联删还是手动删,都能捕获到要删除的图片,维护成本低。

方案二:替换数据库级联删除,改用应用层级联删除

  • 去掉数据库外键的ON DELETE CASCADE配置,在应用层通过事务实现级联删除逻辑。比如删除User时,在服务类里写如下逻辑:
import Database from '@ioc:Adonis/Lucid/Database'
import S3 from '@ioc:Adonis/Addons/S3'

class UserService {
  async delete(userId) {
    await Database.transaction(async (trx) => {
      // 获取所有关联的PostImage记录
      const postImages = await Database.from('post_images')
        .join('posts', 'post_images.post_id', 'posts.id')
        .join('blogs', 'posts.blog_id', 'blogs.id')
        .where('blogs.user_id', userId)
        .select('post_images.s3_url')

      // 批量删除S3文件
      if (postImages.length > 0) {
        await S3.deleteObjects({
          Bucket: '你的存储桶名称',
          Delete: {
            Objects: postImages.map(img => ({ Key: img.s3_url.split('/').pop() })) // 根据实际URL格式提取对象Key
          }
        })
      }

      // 按从下到上的顺序删除数据库记录
      await Database.from('post_images').whereIn('post_id', /* 关联的Post ID列表 */).transacting(trx).delete()
      await Database.from('posts').whereIn('blog_id', /* 关联的Blog ID列表 */).transacting(trx).delete()
      await Database.from('blogs').where('user_id', userId).transacting(trx).delete()
      await Database.from('users').where('id', userId).transacting(trx).delete()
    })
  }
}
  • 同时在PostImage模型的beforeDelete钩子中添加单条删除时的S3文件删除逻辑,覆盖手动删除单张图片的场景。

这个方案所有删除逻辑都在应用层可控,能保证数据库记录和S3文件的一致性,但需要重构现有删除逻辑,适合关联关系不是极度复杂的场景。

方案三:PostgreSQL LISTEN/NOTIFY + 实时监听进程

  • 步骤1:修改触发器函数,当post_images记录被删除时,发送数据库通知:
CREATE OR REPLACE FUNCTION notify_post_image_deleted()
RETURNS TRIGGER AS $$
BEGIN
  PERFORM pg_notify('post_image_deleted', OLD.s3_url);
  RETURN OLD;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trigger_post_image_deleted
AFTER DELETE ON post_images
FOR EACH ROW EXECUTE FUNCTION notify_post_image_deleted();
  • 步骤2:在AdonisJS中创建自定义命令,启动独立监听进程,连接PostgreSQL并监听post_image_deleted频道,收到消息后立即删除对应S3文件:
// commands/ListenPostImageDeletion.ts
import { BaseCommand } from '@adonisjs/core/build/standalone'
import Database from '@ioc:Adonis/Lucid/Database'
import S3 from '@ioc:Adonis/Addons/S3'

export default class ListenPostImageDeletion extends BaseCommand {
  public static commandName = 'listen:post-image-deletion'

  public static description = '监听PostgreSQL发送的图片删除通知'

  public async run() {
    const client = await Database.rawConnection()
    await client.query('LISTEN post_image_deleted')

    client.connection.on('notification', async (msg) => {
      const s3Url = msg.payload
      const key = s3Url.split('/').pop() // 提取S3对象Key
      await S3.deleteObject({
        Bucket: '你的存储桶名称',
        Key: key
      })
      this.logger.info(`已删除S3文件: ${key}`)
    })

    this.logger.info('开始监听图片删除通知...')
  }
}
  • 部署时用PM2等工具保证监听进程持续运行。

这个方案能实时删除S3文件,避免文件长时间孤立,但需要额外维护一个监听进程,适合对实时性要求高的场景。


内容的提问来源于stack exchange,提问作者Obvious_Grapefruit

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 12:03:39