图片批量管理

tim-qtp...大约 2 分钟云图库项目

类似以下效果:

代码如下:

开发批量修改图片服务,依次完成参数校验、空间权限校验、图片查询、批量更新操作:4AeqD6wp8ZemHEHZXii0QseGHx9Ks4t5/a/SqKw4Xao=

// 1. 查询指定图片,仅选择需要的字段
List<Picture> pictureList = this.lambdaQuery()
    .select(Picture::getId, Picture::getSpaceId)
    .eq(Picture::getSpaceId, spaceId)
    .in(Picture::getId, pictureIdList)
    .list();

if (pictureList.isEmpty()) {
    return;
}

// 2. 更新分类和标签
pictureList.forEach(picture -> {
    if (StrUtil.isNotBlank(category)) {
        picture.setCategory(category);
    }
    if (CollUtil.isNotEmpty(tags)) {
        picture.setTags(JSONUtil.toJsonStr(tags));
    }
});

// 3. 批量更新
boolean result = this.updateBatchById(pictureList);
ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);

对这个项目来说,用户要处理的数据量不大,上述代码已经能够满足需求。但如果要处理大量数据,可以使用线程池 + 分批 + 并发进行优化,参考代码如下:

@Resource
private ThreadPoolExecutor customExecutor;

/**
 * 批量编辑图片分类和标签
 */
@Override
@Transactional(rollbackFor = Exception.class)
public void batchEditPictureMetadata(PictureBatchEditRequest request, Long spaceId, Long loginUserId) {
    // 参数校验
    validateBatchEditRequest(request, spaceId, loginUserId);

    // 查询空间下的图片
    List<Picture> pictureList = this.lambdaQuery()
            .eq(Picture::getSpaceId, spaceId)
            .in(Picture::getId, request.getPictureIds())
            .list();

    if (pictureList.isEmpty()) {
        throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "指定的图片不存在或不属于该空间");
    }

    // 分批处理避免长事务
    int batchSize = 100;
    List<CompletableFuture<Void>> futures = new ArrayList<>();
    for (int i = 0; i < pictureList.size(); i += batchSize) {
        List<Picture> batch = pictureList.subList(i, Math.min(i + batchSize, pictureList.size()));

        // 异步处理每批数据
        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            batch.forEach(picture -> {
                // 编辑分类和标签
                if (request.getCategory() != null) {
                    picture.setCategory(request.getCategory());
                }
                if (request.getTags() != null) {
                    picture.setTags(String.join(",", request.getTags()));
                }
            });
            boolean result = this.updateBatchById(batch);
            if (!result) {
                throw new BusinessException(ErrorCode.OPERATION_ERROR, "批量更新图片失败");
            }
        }, customExecutor);

        futures.add(future);
    }

    // 等待所有任务完成
    CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
}

1. 分批处理避免长事务

  • batchSize = 100:每批次处理 100 条数据。
  • pictureList:需要处理的图片列表。
  • subList:将 pictureList 分成多个小批次,每批次最多包含 batchSize 条数据。

2. 异步处理每批数据

CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    batch.forEach(picture -> {
        // 编辑分类和标签
        if (request.getCategory() != null) {
            picture.setCategory(request.getCategory());
        }
        if (request.getTags() != null) {
            picture.setTags(String.join(",", request.getTags()));
        }
    });
    boolean result = this.updateBatchById(batch);
    if (!result) {
        throw new BusinessException(ErrorCode.OPERATION_ERROR, "批量更新图片失败");
    }
}, customExecutor);
  • CompletableFuture.runAsync:异步执行任务。
  • batch.forEach:对每批次中的每张图片进行处理:
    • 如果请求中有分类信息(request.getCategory()),则更新图片的分类。
    • 如果请求中有标签信息(request.getTags()),则将标签拼接成字符串并更新图片的标签。
  • updateBatchById(batch):批量更新数据库中的图片数据。
  • BusinessException:如果更新失败,抛出业务异常。

3. 将异步任务加入列表

java

复制

futures.add(future);
  • 将每个批次的异步任务(CompletableFuture)加入 futures 列表,方便后续统一管理。

4. 等待所有任务完成

java

复制

CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
  • CompletableFuture.allOf:等待所有异步任务完成。
  • join():阻塞当前线程,直到所有任务执行完毕。

5. 线程池配置

java

复制

customExecutor
  • customExecutor:自定义的线程池,用于控制异步任务的并发度。
  • 如果没有显式传入线程池,CompletableFuture 会使用默认的 ForkJoinPool