企业级解决方案九-商品图片自动检索与自动上架
在当今电商竞争日益激烈的环境下,商品图片的质量和展示效果直接影响着转化率和用户体验。对于中小商家而言,每天需要处理大量的商品图片:从网络搜索素材、去水印、添加品牌标识、提升分辨率到批量处理,这些工作如果全部由人工完成,不仅效率低下,而且成本高昂。
本文将深入探讨如何使用Java语言构建一套完整的自动化图片处理系统,涵盖图片网络搜索、智能下载、AI图像处理(去水印、超分、品牌植入)等核心功能。整套方案基于阿里云通义万相API实现,代码可直接应用于生产环境。
一、系统需求分析与架构设计
1.1 业务痛点分析
传统电商图片处理流程存在以下问题:
-
人工搜索效率低:运营人员需要手动浏览多个网站寻找合适的商品图片素材
-
图片质量参差不齐:网络下载的图片往往分辨率不足,带有水印或其他平台的标识
-
批量处理困难:缺少自动化工具,无法高效处理成百上千张图片
-
品牌一致性差:手动添加Logo和文字容易出错,难以保证风格统一
1.2 系统功能目标
基于以上痛点,我们需要实现以下核心功能:
-
自动图片下载:输入图片URL,自动下载到本地指定目录
-
AI图像处理:去水印、超分辨率增强、品牌文字添加
-
异步任务管理:支持长时间处理的异步提交和结果轮询
-
批量处理能力:支持多张图片的并发处理
-
容错与重试:网络异常情况下的自动重试机制
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的电商图片自动化处理系统,包含以下核心模块:
-
图片下载模块:支持HTTP/HTTPS,具备重试机制、断点续传、批量下载能力
-
AI处理模块:集成阿里云通义万相API,实现去水印、超分、文字添加等功能
-
流程编排:将下载、处理、保存串联成完整的自动化流水线
-
扩展能力:支持批量处理、REST API封装、配置化管理
7.2 成本分析
| 服务 | 计费方式 | 预估成本 |
|---|---|---|
| 图片下载 | 免费 | 0元 |
| 通义万相API | 按次计费 | 约0.1-0.5元/张 |
| 服务器 | 按需 | 约100元/月 |
批量处理1000张图片的预估成本:100-500元(API费用)+ 服务器费用
7.3 未来展望
-
多模型支持:集成Stable Diffusion、Midjourney等多款AI绘图工具
-
智能搜索:自动从电商平台搜索相关商品图片
-
A/B测试:自动生成多个版本,测试最优转化效果
-
实时处理:使用消息队列实现高并发下的实时处理
附录:完整项目结构
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>
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)