解决 IOS HEIC原图上传 OSS 后H5/浏览器无法通过链接访问显示的问题

解决 IOS HEIC原图上传 OSS 后无法通过链接访问显示的问题

摘要

在 iOS 平台上,用户选择原图上传至阿里云 OSS 后,其他平台(如 Android、H5)无法通过链接访问和显示该图片。主要原因是 iOS 原图文件较大,且可能包含非标准格式的元数据,导致其他平台无法正确解析HEIC格式图片。本文将提供一套完整的解决方案,涵盖以下关键步骤:

  1. iOS 端上传前压缩图片:确保图片大小在 20M 以内,最好是 1M 或 2M 以内。
  2. 特殊后缀标记:在文件名中加入 _IOS 后缀,便于服务端识别和处理。
  3. 服务端图片处理接口:将标记有特殊后缀的图片进行格式转换(如 JPG、PNG),并持久化到 OSS。
  4. 异步处理与生产者-消费者模型:通过分布式队列加速图片处理任务。
  5. 限速机制:使用 Redisson 的 RRateLimiter 控制请求速率,避免超过 OSS 的 50 QPS 限制。

通过以上方案,您可以有效解决 iOS 原图上传 OSS 后无法通过链接访问显示的问题。


技术亮点

  • 利用OSS图片处理持久化API
  • 服务端无需下载再上传oss
  • 分布式处理,异步无阻塞可重试

解决方案

1. iOS 端上传前压缩图片

iOS 端在上传图片前,需要对图片进行压缩,确保图片大小在 20M 以内,最好是 1M 或 2M 以内。可以使用以下代码进行图片压缩:

func compressImage(image: UIImage, maxFileSize: Int) -> Data? {
    var compression: CGFloat = 1.0
    let maxCompression: CGFloat = 0.1
    let maxSize = maxFileSize * 1024 * 1024 // 转换为字节
    
    var imageData = image.jpegData(compressionQuality: compression)
    
    while (imageData?.count ?? 0) > maxSize && compression > maxCompression {
        compression -= 0.1
        imageData = image.jpegData(compressionQuality: compression)
    }
    
    return imageData
}

2. 特殊后缀标记

iOS 端在上传原图时,在文件名中加入特殊后缀 _IOS,以便服务端识别并进行处理。

let originalFileName = "example.jpg"
let markedFileName = originalFileName.replacingOccurrences(of: ".", with: "_IOS.")

3. 服务端图片处理接口

服务端需要开发一个图片处理接口,用于将标记有特殊后缀的 iOS 原图进行格式转换(如转换为 JPG 或 PNG),并持久化到 OSS。转换后去掉 _IOS 后缀。

3.1 图片处理命令拼接

使用以下方法拼接 OSS 图片处理的命令参数:

/**
 * 拼接处理图片格式转换命令参数
 *
 * @param bucketName OSS Bucket 名称
 * @param styleType  图片处理命令(如 "image/format,jpg")
 * @param targetKey  处理后持久化的目标文件 Key
 * @return 拼接后的图片处理命令
 */
private static StringBuilder processStyle(String bucketName, String styleType, String targetKey) {
    StringBuilder sbStyle = new StringBuilder();
    Formatter styleFormatter = new Formatter(sbStyle);
    styleFormatter.format("%s|sys/saveas,o_%s,b_%s", styleType,
        BinaryUtil.toBase64String(targetKey.getBytes()),
        BinaryUtil.toBase64String(bucketName.getBytes()));
    log.info(sbStyle.toString());
    return sbStyle;
}
3.2 图片格式转换与持久化

使用以下方法实现图片格式转换与持久化:

/**
 * 图片格式转换
 * 1. 先根据目标格式转换,成功后结束
 * 2. 异常后尝试向 PNG 转换,失败后不再重试
 *
 * @param bucketName 原文件所在 Bucket
 * @param key        原文件 Key
 * @param targetKey  目标文件 Key
 * @param format     目标格式(如 "jpg", "png")
 */
private void processImageFormat(String bucketName, String key, String targetKey, String format) {
    try {
        String styleType = String.format("image/format,%s", format);
        StringBuilder sbStyle = processStyle(bucketName, styleType, targetKey);

        ProcessObjectRequest request = new ProcessObjectRequest(bucketName, key, sbStyle.toString());
        GenericResult processResult = ossClient.processObject(request);

        String json = IOUtils.readStreamAsString(processResult.getResponse().getContent(), "UTF-8");
        processResult.getResponse().getContent().close();
        log.info("处理图片 {} 转换结果 {}", key, json);
    } catch (Exception e) {
        log.warn("图片格式转换失败 key {} format {}", key, format, e);

        // PNG 格式化兜底,如果 PNG 也失败了就不再重试
        if (!StrUtil.equalsIgnoreCase("png", format)) {
            processImageFormat(bucketName, key, targetKey, "png");
        }
    }
}

4. 异步处理与生产者-消费者模型

为了加速图片处理,可以采用生产者-消费者模型,将图片处理任务放入队列中,由多个消费者并行处理。

4.1 使用 Redisson 实现分布式队列
import org.redisson.Redisson;
import org.redisson.api.RBlockingQueue;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;

public class ImageProcessingQueue {
    private RedissonClient redissonClient;
    private RBlockingQueue<String> queue;

    public ImageProcessingQueue() {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        redissonClient = Redisson.create(config);
        queue = redissonClient.getBlockingQueue("imageProcessingQueue");
    }

    public void addTask(String imageUrl) {
        queue.add(imageUrl);
    }

    public String takeTask() throws InterruptedException {
        return queue.take();
    }

    public void shutdown() {
        redissonClient.shutdown();
    }
}
4.2 消费者处理任务
public class ImageProcessingConsumer implements Runnable {
    private ImageProcessingQueue queue;

    public ImageProcessingConsumer(ImageProcessingQueue queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while (true) {
            try {
                String imageUrl = queue.takeTask();
                // 处理图片
                processImage(imageUrl);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
    }

    private void processImage(String imageUrl) {
        // 调用图片处理接口
        // ...
    }
}

5. 使用 RRateLimiter 进行限速

为了避免对 OSS 图片处理接口的请求过载,可以使用 Redisson 的 RRateLimiter 来实现限速功能。

5.1 初始化 RRateLimiter
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@Component
public class RateLimiterService {

    @Autowired
    private RedissonClient redissonClient;

    private RRateLimiter rateLimiter;

    @PostConstruct
    public void init() {
        rateLimiter = redissonClient.getRateLimiter("ossImageProcessingRateLimiter");
        rateLimiter.trySetRate(RateType.OVERALL, 50, 1, RateIntervalUnit.SECONDS);
    }

    public boolean tryAcquire() {
        return rateLimiter.tryAcquire();
    }
}
5.2 在图片处理接口中应用限速
@PostMapping("/process")
public String processImage(@RequestBody ImageProcessRequest request) {
    if (!rateLimiterService.tryAcquire()) {
        throw new RuntimeException("请求速率过高,请稍后重试");
    }

    String processedImageUrl = imageProcessingService.processImage(request.getImageUrl(), request.getFileName());
    return processedImageUrl;
}

参考文档


通过以上方案,您可以有效解决 iOS 原图上传 OSS 后无法通过链接访问显示的问题。如果您有任何疑问或建议,欢迎在评论区留言!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值