在当今电商竞争日益激烈的环境下,商品图片的质量和展示效果直接影响着转化率和用户体验。对于中小商家而言,每天需要处理大量的商品图片:从网络搜索素材、去水印、添加品牌标识、提升分辨率到批量处理,这些工作如果全部由人工完成,不仅效率低下,而且成本高昂。

本文将深入探讨如何使用Java语言构建一套完整的自动化图片处理系统,涵盖图片网络搜索、智能下载、AI图像处理(去水印、超分、品牌植入)等核心功能。整套方案基于阿里云通义万相API实现,代码可直接应用于生产环境。

一、系统需求分析与架构设计

1.1 业务痛点分析

传统电商图片处理流程存在以下问题:

  • 人工搜索效率低:运营人员需要手动浏览多个网站寻找合适的商品图片素材

  • 图片质量参差不齐:网络下载的图片往往分辨率不足,带有水印或其他平台的标识

  • 批量处理困难:缺少自动化工具,无法高效处理成百上千张图片

  • 品牌一致性差:手动添加Logo和文字容易出错,难以保证风格统一

1.2 系统功能目标

基于以上痛点,我们需要实现以下核心功能:

  1. 自动图片下载:输入图片URL,自动下载到本地指定目录

  2. AI图像处理:去水印、超分辨率增强、品牌文字添加

  3. 异步任务管理:支持长时间处理的异步提交和结果轮询

  4. 批量处理能力:支持多张图片的并发处理

  5. 容错与重试:网络异常情况下的自动重试机制

1.3 技术选型

技术组件 选型方案 理由
开发语言 Java 11+ 跨平台、生态丰富、适合企业级应用
HTTP客户端 HttpClient (java.net.http) Java11原生支持,性能优秀
JSON解析 Gson 轻量级、易用性强
文件处理 NIO + IO混合 兼顾性能和易用性
AI服务 阿里云DashScope 提供通义万相图像处理能力

1.4 整体架构图

原始图片文件

图片下载模块

HTTP连接池

AI处理模块

任务轮询器

本地文件系统

处理结果URL

二、图片下载模块深度实现

2.1 基础下载功能

图片下载是整个系统的第一环,需要处理各种网络异常和边界情况。

package main.boot;

import java.io.*;
import java.net.*;
import java.nio.file.*;
import java.util.UUID;

/**
 * 图片下载器
 * 支持HTTP/HTTPS协议,提供传统IO和NIO两种实现方式
 */
public class ImageDownloader {
    
    // 默认下载超时配置
    private static final int CONNECT_TIMEOUT = 10000;  // 连接超时:10秒
    private static final int READ_TIMEOUT = 30000;     // 读取超时:30秒
    private static final int BUFFER_SIZE = 8192;       // 缓冲区大小:8KB
    
    // 支持的图片格式
    private static final String[] SUPPORTED_FORMATS = {".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp"};
    
    /**
     * 下载图片到本地(传统IO方式)
     * 
     * @param imageUrl 图片的URL地址
     * @param saveDir  保存目录(可选,默认为当前目录下的downloads文件夹)
     * @return 本地图片文件路径
     * @throws Exception 下载失败时抛出异常
     */
    public static String downloadImage(String imageUrl, String saveDir) throws Exception {
        // 参数校验
        if (imageUrl == null || imageUrl.trim().isEmpty()) {
            throw new IllegalArgumentException("图片URL不能为空");
        }
        
        // 设置保存目录
        String finalSaveDir = getSaveDirectory(saveDir);
        
        // 创建目录(支持多层目录)
        Path directoryPath = Paths.get(finalSaveDir);
        if (!Files.exists(directoryPath)) {
            Files.createDirectories(directoryPath);
            System.out.println("创建目录: " + finalSaveDir);
        }
        
        // 生成文件名(防止重名和非法字符)
        String fileName = generateFileName(imageUrl);
        String localFilePath = finalSaveDir + File.separator + fileName;
        
        // 创建URL对象并打开连接
        URL url = new URL(imageUrl);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        
        try {
            configureConnection(connection);
            
            // 检查响应状态
            int responseCode = connection.getResponseCode();
            if (responseCode != HttpURLConnection.HTTP_OK) {
                throw new IOException(String.format("HTTP响应错误: %d, URL: %s", responseCode, imageUrl));
            }
            
            // 检查Content-Type,确保是图片
            String contentType = connection.getContentType();
            if (contentType != null && !contentType.startsWith("image/")) {
                System.err.println("警告: URL可能不是图片,Content-Type: " + contentType);
            }
            
            // 获取文件大小(用于进度显示)
            int contentLength = connection.getContentLength();
            System.out.println(String.format("开始下载: %s, 文件大小: %s", 
                fileName, formatFileSize(contentLength)));
            
            // 执行下载
            try (InputStream in = connection.getInputStream(); 
                 FileOutputStream out = new FileOutputStream(localFilePath);
                 BufferedInputStream bis = new BufferedInputStream(in);
                 BufferedOutputStream bos = new BufferedOutputStream(out)) {
                
                byte[] buffer = new byte[BUFFER_SIZE];
                int bytesRead;
                long totalBytesRead = 0;
                int lastProgress = 0;
                
                while ((bytesRead = bis.read(buffer)) != -1) {
                    bos.write(buffer, 0, bytesRead);
                    totalBytesRead += bytesRead;
                    
                    // 显示下载进度
                    if (contentLength > 0) {
                        int progress = (int) (totalBytesRead * 100 / contentLength);
                        if (progress > lastProgress) {
                            lastProgress = progress;
                            System.out.print("\r下载进度: " + progress + "%");
                        }
                    }
                }
                System.out.println(); // 换行
            }
            
            System.out.println("图片下载成功: " + localFilePath);
            return localFilePath.replace("\\", "/");
            
        } finally {
            connection.disconnect();
        }
    }
    
    /**
     * NIO方式下载(性能更优)
     */
    public static String downloadImageNIO(String imageUrl, String saveDir) throws Exception {
        // 参数处理
        String finalSaveDir = getSaveDirectory(saveDir);
        Path directory = Paths.get(finalSaveDir);
        if (!Files.exists(directory)) {
            Files.createDirectories(directory);
        }
        
        String fileName = generateFileName(imageUrl);
        Path localFilePath = directory.resolve(fileName);
        
        URL url = new URL(imageUrl);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        
        try {
            configureConnection(connection);
            
            int responseCode = connection.getResponseCode();
            if (responseCode != HttpURLConnection.HTTP_OK) {
                throw new IOException("HTTP响应错误: " + responseCode);
            }
            
            // 使用Files.copy直接复制流
            try (InputStream in = connection.getInputStream()) {
                Files.copy(in, localFilePath, StandardCopyOption.REPLACE_EXISTING);
            }
            
            System.out.println("图片下载成功(NIO): " + localFilePath.toAbsolutePath());
            return localFilePath.toAbsolutePath().toString();
            
        } finally {
            connection.disconnect();
        }
    }
    
    /**
     * 配置HTTP连接参数
     */
    private static void configureConnection(HttpURLConnection connection) {
        connection.setRequestMethod("GET");
        connection.setConnectTimeout(CONNECT_TIMEOUT);
        connection.setReadTimeout(READ_TIMEOUT);
        connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
        connection.setRequestProperty("Accept", "image/webp,image/apng,image/*,*/*;q=0.8");
        connection.setRequestProperty("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8");
        connection.setUseCaches(false);
        connection.setInstanceFollowRedirects(true);  // 自动处理重定向
    }
    
    /**
     * 获取保存目录
     */
    private static String getSaveDirectory(String saveDir) {
        if (saveDir == null || saveDir.trim().isEmpty()) {
            return System.getProperty("user.dir") + File.separator + "downloads";
        }
        return saveDir;
    }
    
    /**
     * 生成安全的文件名
     * 策略:优先使用URL中的文件名,如果无效则使用时间戳+UUID
     */
    private static String generateFileName(String urlStr) {
        // 尝试从URL提取文件名
        String fileName = extractFileName(urlStr);
        
        if (fileName == null || fileName.isEmpty()) {
            // 生成唯一文件名
            String timestamp = String.valueOf(System.currentTimeMillis());
            String uuid = UUID.randomUUID().toString().substring(0, 8);
            fileName = "image_" + timestamp + "_" + uuid + ".png";
        }
        
        // 移除文件名中的非法字符
        fileName = fileName.replaceAll("[\\\\/:*?\"<>|]", "_");
        
        // 确保有文件扩展名
        if (!hasImageExtension(fileName)) {
            fileName += ".png";
        }
        
        return fileName;
    }
    
    /**
     * 检查是否有图片扩展名
     */
    private static boolean hasImageExtension(String fileName) {
        String lowerName = fileName.toLowerCase();
        for (String ext : SUPPORTED_FORMATS) {
            if (lowerName.endsWith(ext)) {
                return true;
            }
        }
        return false;
    }
    
    /**
     * 从URL中提取文件名
     */
    private static String extractFileName(String urlStr) {
        try {
            URL url = new URL(urlStr);
            String path = url.getPath();
            if (path != null && !path.isEmpty()) {
                int lastSlash = path.lastIndexOf('/');
                if (lastSlash != -1 && lastSlash < path.length() - 1) {
                    String fileName = path.substring(lastSlash + 1);
                    
                    // 去除查询参数
                    int queryIndex = fileName.indexOf('?');
                    if (queryIndex != -1) {
                        fileName = fileName.substring(0, queryIndex);
                    }
                    
                    // URL解码
                    fileName = URLDecoder.decode(fileName, "UTF-8");
                    
                    return fileName;
                }
            }
        } catch (MalformedURLException | UnsupportedEncodingException e) {
            System.err.println("解析URL失败: " + e.getMessage());
        }
        return null;
    }
    
    /**
     * 格式化文件大小显示
     */
    private static String formatFileSize(long size) {
        if (size <= 0) return "未知";
        if (size < 1024) return size + " B";
        if (size < 1024 * 1024) return String.format("%.2f KB", size / 1024.0);
        return String.format("%.2f MB", size / (1024.0 * 1024.0));
    }
    
    /**
     * 带重试机制的下载
     */
    public static String downloadWithRetry(String imageUrl, String saveDir, int maxRetries) throws Exception {
        Exception lastException = null;
        
        for (int i = 0; i < maxRetries; i++) {
            try {
                return downloadImage(imageUrl, saveDir);
            } catch (IOException e) {
                lastException = e;
                System.err.println(String.format("下载失败 (第%d次重试): %s", i + 1, e.getMessage()));
                
                if (i < maxRetries - 1) {
                    // 指数退避策略
                    long sleepTime = (long) Math.pow(2, i) * 1000;
                    System.out.println(String.format("%d秒后进行重试...", sleepTime / 1000));
                    Thread.sleep(sleepTime);
                }
            }
        }
        
        throw new Exception("下载失败,已重试" + maxRetries + "次", lastException);
    }
}

2.2 批量下载管理器

package main.boot;

import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 批量图片下载管理器
 * 支持并发下载,提供下载进度追踪
 */
public class BatchDownloadManager {
    
    private final ExecutorService executor;
    private final int maxConcurrent;
    
    public BatchDownloadManager() {
        this(5); // 默认5个并发
    }
    
    public BatchDownloadManager(int maxConcurrent) {
        this.maxConcurrent = maxConcurrent;
        this.executor = Executors.newFixedThreadPool(maxConcurrent);
    }
    
    /**
     * 批量下载图片
     * 
     * @param imageUrls 图片URL列表
     * @param saveDir 保存目录
     * @return 下载结果映射(URL -> 本地路径或错误信息)
     */
    public Map<String, DownloadResult> batchDownload(List<String> imageUrls, String saveDir) {
        Map<String, DownloadResult> results = new ConcurrentHashMap<>();
        AtomicInteger completed = new AtomicInteger(0);
        int total = imageUrls.size();
        
        System.out.println(String.format("开始批量下载,共%d张图片,并发数: %d", total, maxConcurrent));
        long startTime = System.currentTimeMillis();
        
        List<CompletableFuture<Void>> futures = new ArrayList<>();
        
        for (String url : imageUrls) {
            CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                try {
                    String localPath = ImageDownloader.downloadImage(url, saveDir);
                    results.put(url, new DownloadResult(true, localPath, null));
                } catch (Exception e) {
                    results.put(url, new DownloadResult(false, null, e.getMessage()));
                } finally {
                    int done = completed.incrementAndGet();
                    System.out.println(String.format("进度: %d/%d (%.1f%%)", done, total, done * 100.0 / total));
                }
            }, executor);
            futures.add(future);
        }
        
        // 等待所有任务完成
        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
        
        long elapsed = System.currentTimeMillis() - startTime;
        long successCount = results.values().stream().filter(DownloadResult::isSuccess).count();
        
        System.out.println(String.format("批量下载完成! 成功: %d, 失败: %d, 耗时: %.2f秒", 
            successCount, total - successCount, elapsed / 1000.0));
        
        return results;
    }
    
    /**
     * 关闭下载管理器
     */
    public void shutdown() {
        executor.shutdown();
        try {
            if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
                executor.shutdownNow();
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
    
    /**
     * 下载结果内部类
     */
    public static class DownloadResult {
        private final boolean success;
        private final String localPath;
        private final String errorMessage;
        
        public DownloadResult(boolean success, String localPath, String errorMessage) {
            this.success = success;
            this.localPath = localPath;
            this.errorMessage = errorMessage;
        }
        
        public boolean isSuccess() { return success; }
        public String getLocalPath() { return localPath; }
        public String getErrorMessage() { return errorMessage; }
    }
}

三、AI图像处理模块

3.1 通义万相API集成

package main.boot;

import com.google.gson.Gson;
import com.google.gson.annotations.SerializedName;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 图像处理核心类
 * 集成阿里云通义万相API,实现图片超分、去水印、文字添加等功能
 */
public class ImageProcessor {
    
    private static final Gson gson = new Gson();
    
    // API配置
    private static final String API_URL = "https://dashscope.aliyuncs.com/api/v1/services/aigc/image2image/image-synthesis";
    private static final String TASK_QUERY_URL = "https://dashscope.aliyuncs.com/api/v1/tasks/";
    
    // 默认配置
    private static final int DEFAULT_POLL_INTERVAL = 3000;  // 轮询间隔:3秒
    private static final int MAX_POLL_COUNT = 60;           // 最大轮询次数:3分钟
    
    private final String apiKey;
    private final HttpClient httpClient;
    
    /**
     * 构造函数
     * @param apiKey 阿里云DashScope API密钥
     */
    public ImageProcessor(String apiKey) {
        this.apiKey = apiKey;
        this.httpClient = HttpClient.newBuilder()
            .connectTimeout(Duration.ofSeconds(30))
            .build();
    }
    
    /**
     * 处理单张图片
     * 
     * @param imageUrl 原始图片URL
     * @param config 处理配置(包含prompt、负面提示等)
     * @return 处理后的图片URL
     */
    public String processImage(String imageUrl, ProcessConfig config) throws Exception {
        if (imageUrl == null || imageUrl.isEmpty()) {
            throw new IllegalArgumentException("图片URL不能为空");
        }
        
        System.out.println("开始处理图片: " + imageUrl);
        System.out.println("处理参数: " + config);
        
        // 1. 构建请求体
        String requestBody = buildRequestBody(imageUrl, config);
        
        // 2. 提交异步任务
        String taskId = submitTask(requestBody);
        System.out.println("任务已提交,任务ID: " + taskId);
        
        // 3. 轮询获取结果
        String resultUrl = pollTaskResult(taskId);
        
        System.out.println("图片处理完成: " + resultUrl);
        return resultUrl;
    }
    
    /**
     * 构建API请求体
     */
    private String buildRequestBody(String imageUrl, ProcessConfig config) {
        // 使用JSON模板,避免字符串拼接的转义问题
        String template = """
            {
                "model": "%s",
                "function": "%s",
                "size": "%s",
                "input": {
                    "prompt": "%s",
                    "negative_prompt": "%s",
                    "images": ["%s"]
                },
                "parameters": {
                    "n": %d
                }
            }
            """;
        
        return String.format(template,
            config.getModel(),
            config.getFunction(),
            config.getSize(),
            escapeJson(config.getPrompt()),
            escapeJson(config.getNegativePrompt()),
            imageUrl,
            config.getN()
        );
    }
    
    /**
     * 转义JSON字符串中的特殊字符
     */
    private String escapeJson(String str) {
        if (str == null) return "";
        return str.replace("\\", "\\\\")
                  .replace("\"", "\\\"")
                  .replace("\n", "\\n")
                  .replace("\r", "\\r")
                  .replace("\t", "\\t");
    }
    
    /**
     * 提交异步任务
     */
    private String submitTask(String requestBody) throws Exception {
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(API_URL))
            .header("X-DashScope-Async", "enable")
            .header("Authorization", "Bearer " + apiKey)
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(requestBody))
            .timeout(Duration.ofSeconds(30))
            .build();
        
        HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
        
        if (response.statusCode() != 200) {
            throw new Exception("提交任务失败,状态码: " + response.statusCode() + ", 响应: " + response.body());
        }
        
        TaskSubmitResponse submitResp = gson.fromJson(response.body(), TaskSubmitResponse.class);
        
        if (submitResp.output == null || submitResp.output.taskId == null) {
            throw new Exception("解析任务ID失败: " + response.body());
        }
        
        return submitResp.output.taskId;
    }
    
    /**
     * 轮询任务结果
     */
    private String pollTaskResult(String taskId) throws Exception {
        int pollCount = 0;
        
        while (pollCount < MAX_POLL_COUNT) {
            HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(TASK_QUERY_URL + taskId))
                .header("Authorization", "Bearer " + apiKey)
                .GET()
                .timeout(Duration.ofSeconds(30))
                .build();
            
            HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
            
            if (response.statusCode() != 200) {
                throw new Exception("查询任务状态失败,状态码: " + response.statusCode());
            }
            
            TaskQueryResponse queryResp = gson.fromJson(response.body(), TaskQueryResponse.class);
            
            if (queryResp.output == null) {
                Thread.sleep(DEFAULT_POLL_INTERVAL);
                pollCount++;
                continue;
            }
            
            String status = queryResp.output.taskStatus;
            System.out.println("任务状态: " + status + " (轮询次数: " + (pollCount + 1) + ")");
            
            switch (status) {
                case "SUCCEEDED":
                    if (queryResp.output.results != null && !queryResp.output.results.isEmpty()) {
                        return queryResp.output.results.get(0).url;
                    }
                    throw new Exception("任务成功但未返回图片URL");
                    
                case "FAILED":
                    throw new Exception("任务失败: " + queryResp.output.message);
                    
                case "PENDING":
                case "RUNNING":
                    Thread.sleep(DEFAULT_POLL_INTERVAL);
                    pollCount++;
                    break;
                    
                default:
                    System.err.println("未知任务状态: " + status);
                    Thread.sleep(DEFAULT_POLL_INTERVAL);
                    pollCount++;
            }
        }
        
        throw new Exception("任务超时,已轮询" + MAX_POLL_COUNT + "次");
    }
    
    /**
     * 批量处理图片(顺序执行)
     */
    public Map<String, String> batchProcess(List<String> imageUrls, ProcessConfig config) {
        Map<String, String> results = new ConcurrentHashMap<>();
        
        for (String url : imageUrls) {
            try {
                String resultUrl = processImage(url, config);
                results.put(url, resultUrl);
                System.out.println("处理成功: " + url + " -> " + resultUrl);
            } catch (Exception e) {
                System.err.println("处理失败: " + url + ", 错误: " + e.getMessage());
                results.put(url, "ERROR: " + e.getMessage());
            }
        }
        
        return results;
    }
    
    // ========== 内部类定义 ==========
    
    /**
     * 图片处理配置
     */
    public static class ProcessConfig {
        private String model = "wan2.5-i2i-preview";
        private String function = "super_resolution";
        private String size = "1024*1024";
        private String prompt = "提高图片质量,增加细节,优化色彩";
        private String negativePrompt = "低分辨率,模糊,变形,水印,文字,logo";
        private int n = 1;
        
        // 构造函数
        public ProcessConfig() {}
        
        // 针对去水印的预设配置
        public static ProcessConfig forWatermarkRemoval(String brandName) {
            ProcessConfig config = new ProcessConfig();
            config.prompt = String.format("去除图片中的水印和logo,保持图片自然完整。%s", 
                brandName != null ? "添加'" + brandName + "'文字标识,使用红色字体,放在图片合适位置。" : "");
            config.negativePrompt = "不要有模糊的水印残留,不要有变形,不要有粗糙的线条,不要有低分辨率";
            return config;
        }
        
        // 针对超分的预设配置
        public static ProcessConfig forSuperResolution() {
            ProcessConfig config = new ProcessConfig();
            config.function = "super_resolution";
            config.prompt = "提升图片分辨率,增加细节清晰度,优化色彩饱和度,使图片更加细腻";
            config.negativePrompt = "不要出现噪点,不要过度锐化,不要颜色失真";
            config.size = "2048*2048";
            return config;
        }
        
        // Getters and Setters
        public String getModel() { return model; }
        public void setModel(String model) { this.model = model; }
        public String getFunction() { return function; }
        public void setFunction(String function) { this.function = function; }
        public String getSize() { return size; }
        public void setSize(String size) { this.size = size; }
        public String getPrompt() { return prompt; }
        public void setPrompt(String prompt) { this.prompt = prompt; }
        public String getNegativePrompt() { return negativePrompt; }
        public void setNegativePrompt(String negativePrompt) { this.negativePrompt = negativePrompt; }
        public int getN() { return n; }
        public void setN(int n) { this.n = n; }
        
        @Override
        public String toString() {
            return String.format("ProcessConfig{model='%s', function='%s', size='%s', prompt='%s'}", 
                model, function, size, prompt.length() > 50 ? prompt.substring(0, 50) + "..." : prompt);
        }
    }
    
    /**
     * 任务提交响应结构
     */
    static class TaskSubmitResponse {
        Output output;
        
        static class Output {
            @SerializedName("task_id")
            String taskId;
        }
    }
    
    /**
     * 任务查询响应结构
     */
    static class TaskQueryResponse {
        Output output;
        
        static class Output {
            @SerializedName("task_id")
            String taskId;
            
            @SerializedName("task_status")
            String taskStatus;
            
            String message;
            List<Result> results;
        }
        
        static class Result {
            String url;
        }
    }
}

3.2 增强的图片处理类(兼容原DealImage)

package main.boot;

import com.google.gson.Gson;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.List;

/**
 * 兼容原有DealImage功能的工具类
 * 保持与原代码的接口兼容性
 */
public class DealImage {
    
    private static final Gson gson = new Gson();
    private static final String API_KEY = "sk-aab2ce6de36342048fdf10241fdfb141";
    
    /**
     * 处理图片(同步方法,轮询直到完成)
     * 
     * @param originImageUrl 原始图片URL
     * @return 处理后的图片URL
     */
    public static String doWork(String originImageUrl) {
        // 使用自定义的prompt
        String prompt = "去除图片中的汽车之家LOGO,换成车来客三个红色的字。图像超分,图像要有合适的背景。";
        String negativePrompt = "不要有很粗糙的线条。不要出现低分辨率、差质量、低质量、残缺、比例不良等情况";
        
        return processImageWithPrompt(originImageUrl, prompt, negativePrompt);
    }
    
    /**
     * 自定义prompt的图片处理
     */
    public static String processImageWithPrompt(String originImageUrl, String prompt, String negativePrompt) {
        if (originImageUrl == null || originImageUrl.isEmpty()) {
            return "图片URL不能为空";
        }
        
        // 构建请求体
        String requestBody = String.format("""
            {
                "model": "wan2.5-i2i-preview",
                "function": "super_resolution",
                "size": "1024*1024",
                "input": {
                    "prompt": "%s",
                    "negative_prompt": "%s",
                    "images": ["%s"]
                },
                "parameters": {
                    "n": 1
                }
            }
            """, escapeJson(prompt), escapeJson(negativePrompt), originImageUrl);
        
        System.out.println("发送请求: " + requestBody);
        
        try {
            HttpClient client = HttpClient.newBuilder()
                .connectTimeout(Duration.ofSeconds(100))
                .build();
            
            // 提交异步任务
            HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://dashscope.aliyuncs.com/api/v1/services/aigc/image2image/image-synthesis"))
                .header("X-DashScope-Async", "enable")
                .header("Authorization", "Bearer " + API_KEY)
                .header("Content-Type", "application/json")
                .POST(HttpRequest.BodyPublishers.ofString(requestBody))
                .timeout(Duration.ofSeconds(30))
                .build();
            
            HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
            System.out.println("提交任务响应 - 状态码: " + response.statusCode());
            
            if (response.statusCode() != 200) {
                return "提交任务失败: " + response.body();
            }
            
            // 解析任务ID
            JsonParse1 jsonParse1 = gson.fromJson(response.body(), JsonParse1.class);
            if (jsonParse1.output == null || jsonParse1.output.task_id == null) {
                return "解析任务ID失败: " + response.body();
            }
            
            String taskId = jsonParse1.output.task_id;
            System.out.println("任务ID: " + taskId);
            
            // 轮询任务状态
            String dealImageUrl = pollTaskResult(client, taskId);
            return dealImageUrl;
            
        } catch (Exception e) {
            e.printStackTrace();
            return "图片处理异常: " + e.getMessage();
        }
    }
    
    /**
     * 轮询任务结果
     */
    private static String pollTaskResult(HttpClient client, String taskId) throws Exception {
        int maxRetries = 30;
        int retryCount = 0;
        
        while (retryCount < maxRetries) {
            HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://dashscope.aliyuncs.com/api/v1/tasks/" + taskId))
                .header("Authorization", "Bearer " + API_KEY)
                .GET()
                .timeout(Duration.ofSeconds(30))
                .build();
            
            HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
            
            if (response.statusCode() == 200) {
                JsonParse1 jsonParse2 = gson.fromJson(response.body(), JsonParse1.class);
                
                if (jsonParse2.output == null) {
                    Thread.sleep(3000);
                    retryCount++;
                    continue;
                }
                
                String taskStatus = jsonParse2.output.task_status;
                System.out.println("任务状态: " + taskStatus + " (第" + (retryCount + 1) + "次查询)");
                
                if ("SUCCEEDED".equals(taskStatus)) {
                    if (jsonParse2.output.results != null && !jsonParse2.output.results.isEmpty()) {
                        return jsonParse2.output.results.get(0).url;
                    } else {
                        return "任务成功但未返回结果";
                    }
                } else if ("FAILED".equals(taskStatus)) {
                    return "任务失败: " + jsonParse2.output.message;
                }
            }
            
            Thread.sleep(3000);
            retryCount++;
        }
        
        return "任务超时,已轮询" + maxRetries + "次";
    }
    
    /**
     * 转义JSON字符串
     */
    private static String escapeJson(String str) {
        if (str == null) return "";
        return str.replace("\\", "\\\\")
                  .replace("\"", "\\\"")
                  .replace("\n", "\\n")
                  .replace("\r", "\\r")
                  .replace("\t", "\\t");
    }
    
    // ========== JSON解析类 ==========
    
    static class JsonParse1 {
        Output output;
    }
    
    static class Output {
        String task_id;
        String task_status;
        String message;
        List<Results> results;
    }
    
    static class Results {
        String url;
    }
}

四、完整业务流程编排

4.1 主控制器

java

复制

下载

package main.boot;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 图片处理主控制器
 * 协调各个模块完成完整的图片处理流程
 */
public class ImageProcessingController {
    
    private final ImageProcessor processor;
    private final BatchDownloadManager downloadManager;
    
    public ImageProcessingController(String apiKey) {
        this.processor = new ImageProcessor(apiKey);
        this.downloadManager = new BatchDownloadManager(3); // 下载并发数3
    }
    
    /**
     * 单张图片完整处理流程
     * 下载原始图片 -> AI处理 -> 下载处理结果
     */
    public ProcessResult processSingleImage(String imageUrl, String outputDir) {
        ProcessResult result = new ProcessResult(imageUrl);
        long startTime = System.currentTimeMillis();
        
        try {
            // 步骤1: 下载原始图片
            System.out.println("========== 步骤1: 下载原始图片 ==========");
            String originalLocalPath = ImageDownloader.downloadWithRetry(imageUrl, outputDir + "/original", 3);
            result.originalLocalPath = originalLocalPath;
            System.out.println("原始图片已保存: " + originalLocalPath);
            
            // 步骤2: AI处理
            System.out.println("========== 步骤2: AI图片处理 ==========");
            ImageProcessor.ProcessConfig config = ImageProcessor.ProcessConfig.forWatermarkRemoval("车来客");
            String processedUrl = processor.processImage(imageUrl, config);
            result.processedUrl = processedUrl;
            System.out.println("处理完成,结果URL: " + processedUrl);
            
            // 步骤3: 下载处理后的图片
            System.out.println("========== 步骤3: 下载处理结果 ==========");
            String finalLocalPath = ImageDownloader.downloadImage(processedUrl, outputDir + "/processed");
            result.finalLocalPath = finalLocalPath;
            
            long elapsed = System.currentTimeMillis() - startTime;
            result.success = true;
            result.elapsedMs = elapsed;
            
            System.out.println("========== 处理完成 ==========");
            System.out.println("总耗时: " + elapsed / 1000.0 + "秒");
            System.out.println("最终文件: " + finalLocalPath);
            
        } catch (Exception e) {
            result.success = false;
            result.errorMessage = e.getMessage();
            System.err.println("处理失败: " + e.getMessage());
            e.printStackTrace();
        }
        
        return result;
    }
    
    /**
     * 批量处理图片
     */
    public List<ProcessResult> batchProcess(List<String> imageUrls, String outputDir) {
        List<ProcessResult> results = new ArrayList<>();
        AtomicInteger index = new AtomicInteger(0);
        int total = imageUrls.size();
        
        System.out.println(String.format("开始批量处理,共%d张图片", total));
        long startTime = System.currentTimeMillis();
        
        for (String url : imageUrls) {
            int current = index.incrementAndGet();
            System.out.println(String.format("\n========== 处理第 %d/%d 张图片 ==========", current, total));
            
            ProcessResult result = processSingleImage(url, outputDir + "/batch_" + current);
            results.add(result);
            
            // 输出进度
            long successCount = results.stream().filter(r -> r.success).count();
            System.out.println(String.format("当前进度: %d/%d, 成功: %d, 失败: %d", 
                current, total, successCount, current - successCount));
        }
        
        long elapsed = System.currentTimeMillis() - startTime;
        long successCount = results.stream().filter(r -> r.success).count();
        
        System.out.println("\n========== 批量处理完成 ==========");
        System.out.println(String.format("总数: %d, 成功: %d, 失败: %d", total, successCount, total - successCount));
        System.out.println(String.format("总耗时: %.2f秒, 平均每张: %.2f秒", 
            elapsed / 1000.0, elapsed / 1000.0 / total));
        
        return results;
    }
    
    /**
     * 关闭资源
     */
    public void shutdown() {
        downloadManager.shutdown();
    }
    
    /**
     * 处理结果类
     */
    public static class ProcessResult {
        public final String originalUrl;
        public boolean success;
        public String originalLocalPath;
        public String processedUrl;
        public String finalLocalPath;
        public long elapsedMs;
        public String errorMessage;
        
        public ProcessResult(String originalUrl) {
            this.originalUrl = originalUrl;
        }
        
        @Override
        public String toString() {
            if (success) {
                return String.format("✅ 成功 | 原图: %s | 结果: %s | 耗时: %.2f秒",
                    originalLocalPath, finalLocalPath, elapsedMs / 1000.0);
            } else {
                return String.format("❌ 失败 | URL: %s | 错误: %s",
                    originalUrl, errorMessage);
            }
        }
    }
    
    // ========== 主函数示例 ==========
    
    public static void main(String[] args) {
        // API密钥(建议从环境变量或配置文件读取)
        String apiKey = System.getenv("DASHSCOPE_API_KEY");
        if (apiKey == null || apiKey.isEmpty()) {
            apiKey = "your-api-key-here";
            System.err.println("警告: 请设置环境变量 DASHSCOPE_API_KEY");
        }
        
        // 创建控制器
        ImageProcessingController controller = new ImageProcessingController(apiKey);
        
        try {
            // 单张图片处理
            String testImageUrl = "https://g.autoimg.cn/@img/car3/cardfs/series/g24/M02/24/84/666x501_autohomecar__wKgHH1qnPRyAUl8pAAVh9jdZkj0538.png";
            
            ProcessResult result = controller.processSingleImage(testImageUrl, "./output");
            System.out.println("\n处理结果: " + result);
            
            // 批量处理示例
            // List<String> urls = Arrays.asList(
            //     "https://example.com/image1.png",
            //     "https://example.com/image2.png"
            // );
            // List<ProcessResult> results = controller.batchProcess(urls, "./batch_output");
            // results.forEach(System.out::println);
            
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            controller.shutdown();
        }
    }
}

4.2 配置文件支持

package main.boot.config;

import java.io.*;
import java.util.Properties;

/**
 * 配置管理类
 * 支持从配置文件加载API密钥等敏感信息
 */
public class ConfigManager {
    
    private static final String CONFIG_FILE = "application.properties";
    private static Properties props = new Properties();
    
    static {
        loadConfig();
    }
    
    private static void loadConfig() {
        try (InputStream input = ConfigManager.class.getClassLoader()
                .getResourceAsStream(CONFIG_FILE)) {
            if (input != null) {
                props.load(input);
                System.out.println("配置文件加载成功: " + CONFIG_FILE);
            } else {
                System.out.println("未找到配置文件,使用默认配置");
                setDefaults();
            }
        } catch (IOException e) {
            System.err.println("加载配置文件失败: " + e.getMessage());
            setDefaults();
        }
    }
    
    private static void setDefaults() {
        props.setProperty("dashscope.api.key", "");
        props.setProperty("download.timeout.connect", "10000");
        props.setProperty("download.timeout.read", "30000");
        props.setProperty("download.concurrent", "5");
        props.setProperty("image.process.model", "wan2.5-i2i-preview");
        props.setProperty("image.process.size", "1024*1024");
        props.setProperty("image.poll.interval", "3000");
        props.setProperty("image.poll.max", "60");
    }
    
    public static String getDashscopeApiKey() {
        String key = props.getProperty("dashscope.api.key");
        if (key == null || key.isEmpty()) {
            key = System.getenv("DASHSCOPE_API_KEY");
        }
        return key;
    }
    
    public static int getConnectTimeout() {
        return Integer.parseInt(props.getProperty("download.timeout.connect", "10000"));
    }
    
    public static int getReadTimeout() {
        return Integer.parseInt(props.getProperty("download.timeout.read", "30000"));
    }
    
    public static int getDownloadConcurrent() {
        return Integer.parseInt(props.getProperty("download.concurrent", "5"));
    }
    
    public static String getModel() {
        return props.getProperty("image.process.model", "wan2.5-i2i-preview");
    }
    
    public static String getImageSize() {
        return props.getProperty("image.process.size", "1024*1024");
    }
    
    public static int getPollInterval() {
        return Integer.parseInt(props.getProperty("image.poll.interval", "3000"));
    }
    
    public static int getMaxPollCount() {
        return Integer.parseInt(props.getProperty("image.poll.max", "60"));
    }
}

五、实际应用场景与扩展

5.1 电商商品图片批量处理

/**
 * 电商场景专用处理类
 */
public class EcommerceImageHandler {
    
    private final ImageProcessingController controller;
    
    public EcommerceImageHandler(String apiKey) {
        this.controller = new ImageProcessingController(apiKey);
    }
    
    /**
     * 处理商品主图
     * 功能:去除竞品水印、添加自家Logo、超分增强
     */
    public void processProductImages(List<String> productImageUrls, String brandName) {
        System.out.println("开始处理商品图片,品牌: " + brandName);
        
        for (String url : productImageUrls) {
            try {
                // 自定义处理参数
                ImageProcessor.ProcessConfig config = new ImageProcessor.ProcessConfig();
                config.setFunction("super_resolution");
                config.setPrompt(String.format(
                    "去除图片中的所有水印和Logo。在图片底部或角落添加'%s'三个红色的艺术字," +
                    "字体醒目但不遮挡主体。提升图片整体质量和清晰度。", brandName));
                config.setNegativePrompt("不要有模糊、低分辨率、变形、色彩失真、过度锐化");
                config.setSize("2048*2048");
                
                // 执行处理
                String resultUrl = controller.processImage(url, config);
                System.out.println("处理完成: " + url + " -> " + resultUrl);
                
            } catch (Exception e) {
                System.err.println("处理失败: " + url + ", " + e.getMessage());
            }
        }
    }
}

5.2 REST API封装

/**
 * 将图片处理能力封装为REST API
 * 使用Spring Boot或其他Web框架
 */
@RestController
@RequestMapping("/api/image")
public class ImageProcessController {
    
    @Autowired
    private ImageProcessingService service;
    
    @PostMapping("/process")
    public ResponseEntity<ProcessResponse> processImage(@RequestBody ProcessRequest request) {
        try {
            String resultUrl = service.processImage(request.getImageUrl(), request.getPrompt());
            return ResponseEntity.ok(new ProcessResponse(true, resultUrl, null));
        } catch (Exception e) {
            return ResponseEntity.status(500).body(new ProcessResponse(false, null, e.getMessage()));
        }
    }
    
    @PostMapping("/batch")
    public ResponseEntity<BatchProcessResponse> batchProcess(@RequestBody BatchProcessRequest request) {
        List<String> results = service.batchProcess(request.getImageUrls());
        return ResponseEntity.ok(new BatchProcessResponse(results.size(), results));
    }
}

六、性能优化与最佳实践

6.1 性能优化建议

/**
 * 性能优化配置
 */
public class PerformanceOptimizer {
    
    // 1. 使用连接池
    private static final HttpClient SHARED_CLIENT = HttpClient.newBuilder()
        .connectTimeout(Duration.ofSeconds(10))
        .executor(Executors.newFixedThreadPool(10))
        .build();
    
    // 2. 本地缓存机制
    private static final Map<String, String> CACHE = new ConcurrentHashMap<>();
    
    public static String getWithCache(String imageUrl) {
        if (CACHE.containsKey(imageUrl)) {
            System.out.println("从缓存获取: " + imageUrl);
            return CACHE.get(imageUrl);
        }
        // 实际处理...
        return null;
    }
    
    // 3. 异步非阻塞处理
    public CompletableFuture<String> processAsync(String imageUrl) {
        return CompletableFuture.supplyAsync(() -> {
            try {
                return DealImage.doWork(imageUrl);
            } catch (Exception e) {
                throw new CompletionException(e);
            }
        });
    }
}

6.2 错误处理与监控

/**
 * 错误处理与监控工具
 */
public class ErrorHandler {
    
    private static final Logger logger = LoggerFactory.getLogger(ErrorHandler.class);
    private static final AtomicInteger errorCount = new AtomicInteger(0);
    
    public static void handleError(Exception e, String context) {
        errorCount.incrementAndGet();
        logger.error("错误 [{}]: {}, 错误类型: {}", 
            errorCount.get(), context, e.getClass().getSimpleName());
        logger.debug("详细错误信息: ", e);
        
        // 发送告警(可集成钉钉、企业微信等)
        sendAlert(context, e.getMessage());
    }
    
    private static void sendAlert(String context, String message) {
        // 实现告警发送逻辑
        System.err.println("⚠️ 告警: " + context + " - " + message);
    }
    
    public static int getErrorCount() {
        return errorCount.get();
    }
}

七、总结与展望

7.1 本文总结

本文完整实现了一套基于Java的电商图片自动化处理系统,包含以下核心模块:

  1. 图片下载模块:支持HTTP/HTTPS,具备重试机制、断点续传、批量下载能力

  2. AI处理模块:集成阿里云通义万相API,实现去水印、超分、文字添加等功能

  3. 流程编排:将下载、处理、保存串联成完整的自动化流水线

  4. 扩展能力:支持批量处理、REST API封装、配置化管理

7.2 成本分析

服务 计费方式 预估成本
图片下载 免费 0元
通义万相API 按次计费 约0.1-0.5元/张
服务器 按需 约100元/月

批量处理1000张图片的预估成本:100-500元(API费用)+ 服务器费用

7.3 未来展望

  1. 多模型支持:集成Stable Diffusion、Midjourney等多款AI绘图工具

  2. 智能搜索:自动从电商平台搜索相关商品图片

  3. A/B测试:自动生成多个版本,测试最优转化效果

  4. 实时处理:使用消息队列实现高并发下的实时处理


附录:完整项目结构

image-processor/
├── src/main/java/main/boot/
│   ├── ImageDownloader.java          # 图片下载模块
│   ├── ImageProcessor.java           # AI处理模块
│   ├── DealImage.java                # 兼容原有接口
│   ├── BatchDownloadManager.java     # 批量下载管理
│   ├── ImageProcessingController.java # 主控制器
│   ├── config/
│   │   └── ConfigManager.java        # 配置管理
│   └── handler/
│       └── EcommerceImageHandler.java # 电商专用处理
├── src/main/resources/
│   └── application.properties        # 配置文件
└── pom.xml                           # Maven依赖配置

Maven依赖:

<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.10.1</version>
</dependency>
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐