1
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
package cn.van.business.controller;
|
||||
|
||||
import cn.van.business.model.ApiResponse;
|
||||
import cn.van.business.service.MarketingImageService;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 营销图片合成控制器
|
||||
* 提供营销图片生成的HTTP接口
|
||||
*
|
||||
* @author System
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/jarvis/marketing-image")
|
||||
public class MarketingImageController {
|
||||
|
||||
@Autowired
|
||||
private MarketingImageService marketingImageService;
|
||||
|
||||
/**
|
||||
* 生成单张营销图片
|
||||
*
|
||||
* POST /jarvis/marketing-image/generate
|
||||
*
|
||||
* 请求体:
|
||||
* {
|
||||
* "productImageUrl": "商品主图URL",
|
||||
* "originalPrice": 499.0,
|
||||
* "finalPrice": 199.0,
|
||||
* "productName": "商品名称(可选)"
|
||||
* }
|
||||
*
|
||||
* 返回:
|
||||
* {
|
||||
* "code": 200,
|
||||
* "msg": "操作成功",
|
||||
* "data": {
|
||||
* "imageBase64": "data:image/jpg;base64,..."
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
@PostMapping("/generate")
|
||||
public JSONObject generateMarketingImage(@RequestBody Map<String, Object> request) {
|
||||
JSONObject response = new JSONObject();
|
||||
try {
|
||||
String productImageUrl = (String) request.get("productImageUrl");
|
||||
Object originalPriceObj = request.get("originalPrice");
|
||||
Object finalPriceObj = request.get("finalPrice");
|
||||
String productName = (String) request.get("productName");
|
||||
|
||||
if (productImageUrl == null || originalPriceObj == null || finalPriceObj == null) {
|
||||
response.put("code", 400);
|
||||
response.put("msg", "缺少必要参数: productImageUrl, originalPrice, finalPrice");
|
||||
return response;
|
||||
}
|
||||
|
||||
Double originalPrice = parseDouble(originalPriceObj);
|
||||
Double finalPrice = parseDouble(finalPriceObj);
|
||||
|
||||
if (originalPrice == null || finalPrice == null) {
|
||||
response.put("code", 400);
|
||||
response.put("msg", "价格参数格式错误");
|
||||
return response;
|
||||
}
|
||||
|
||||
String base64Image = marketingImageService.generateMarketingImage(
|
||||
productImageUrl, originalPrice, finalPrice, productName);
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("imageBase64", base64Image);
|
||||
|
||||
response.put("code", 200);
|
||||
response.put("msg", "操作成功");
|
||||
response.put("data", data);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("生成营销图片失败", e);
|
||||
response.put("code", 500);
|
||||
response.put("msg", "生成营销图片失败: " + e.getMessage());
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量生成营销图片
|
||||
*
|
||||
* POST /jarvis/marketing-image/batch-generate
|
||||
*
|
||||
* 请求体:
|
||||
* {
|
||||
* "requests": [
|
||||
* {
|
||||
* "productImageUrl": "商品主图URL1",
|
||||
* "originalPrice": 499.0,
|
||||
* "finalPrice": 199.0,
|
||||
* "productName": "商品名称1(可选)"
|
||||
* },
|
||||
* {
|
||||
* "productImageUrl": "商品主图URL2",
|
||||
* "originalPrice": 699.0,
|
||||
* "finalPrice": 349.0,
|
||||
* "productName": "商品名称2(可选)"
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
*
|
||||
* 返回:
|
||||
* {
|
||||
* "code": 200,
|
||||
* "msg": "操作成功",
|
||||
* "data": {
|
||||
* "results": [
|
||||
* {
|
||||
* "success": true,
|
||||
* "imageBase64": "data:image/jpg;base64,...",
|
||||
* "index": 0
|
||||
* },
|
||||
* {
|
||||
* "success": false,
|
||||
* "error": "错误信息",
|
||||
* "index": 1
|
||||
* }
|
||||
* ],
|
||||
* "total": 2,
|
||||
* "successCount": 1,
|
||||
* "failCount": 1
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
@PostMapping("/batch-generate")
|
||||
public JSONObject batchGenerateMarketingImages(@RequestBody Map<String, Object> request) {
|
||||
JSONObject response = new JSONObject();
|
||||
try {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> requests = (List<Map<String, Object>>) request.get("requests");
|
||||
|
||||
if (requests == null || requests.isEmpty()) {
|
||||
response.put("code", 400);
|
||||
response.put("msg", "请求列表不能为空");
|
||||
return response;
|
||||
}
|
||||
|
||||
Map<String, Object> result = marketingImageService.batchGenerateMarketingImages(requests);
|
||||
|
||||
response.put("code", 200);
|
||||
response.put("msg", "操作成功");
|
||||
response.put("data", result);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("批量生成营销图片失败", e);
|
||||
response.put("code", 500);
|
||||
response.put("msg", "批量生成营销图片失败: " + e.getMessage());
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析Double值
|
||||
*/
|
||||
private Double parseDouble(Object value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (value instanceof Double) {
|
||||
return (Double) value;
|
||||
}
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).doubleValue();
|
||||
}
|
||||
try {
|
||||
return Double.parseDouble(value.toString());
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
433
src/main/java/cn/van/business/service/MarketingImageService.java
Normal file
433
src/main/java/cn/van/business/service/MarketingImageService.java
Normal file
@@ -0,0 +1,433 @@
|
||||
package cn.van.business.service;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import cn.van.business.util.ds.DeepSeekClientUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.coobird.thumbnailator.Thumbnails;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 营销图片合成服务
|
||||
* 用于生成小红书等平台的营销对比图
|
||||
*
|
||||
* @author System
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class MarketingImageService {
|
||||
|
||||
@Autowired
|
||||
private DeepSeekClientUtil deepSeekClientUtil;
|
||||
|
||||
// 输出图片尺寸
|
||||
private static final int OUTPUT_WIDTH = 1080;
|
||||
private static final int OUTPUT_HEIGHT = 1080;
|
||||
|
||||
// 字体配置(支持回退)
|
||||
private static final String[] FONT_NAMES = {"Microsoft YaHei", "SimHei", "Arial", Font.SANS_SERIF}; // 字体优先级
|
||||
private static final int ORIGINAL_PRICE_FONT_SIZE = 36; // 官网价字体大小
|
||||
private static final int FINAL_PRICE_FONT_SIZE = 72; // 到手价字体大小
|
||||
private static final int PRODUCT_NAME_FONT_SIZE = 32; // 商品名称字体大小
|
||||
|
||||
/**
|
||||
* 获取可用字体
|
||||
*/
|
||||
private Font getAvailableFont(int style, int size) {
|
||||
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
|
||||
String[] availableFonts = ge.getAvailableFontFamilyNames();
|
||||
|
||||
for (String fontName : FONT_NAMES) {
|
||||
for (String available : availableFonts) {
|
||||
if (available.equals(fontName)) {
|
||||
return new Font(fontName, style, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 如果都不可用,使用默认字体
|
||||
return new Font(Font.SANS_SERIF, style, size);
|
||||
}
|
||||
|
||||
// 颜色配置
|
||||
private static final Color ORIGINAL_PRICE_COLOR = new Color(153, 153, 153); // 灰色 #999999
|
||||
private static final Color FINAL_PRICE_COLOR = new Color(255, 0, 0); // 红色 #FF0000
|
||||
private static final Color PRODUCT_NAME_COLOR = new Color(51, 51, 51); // 深灰色 #333333
|
||||
private static final Color BACKGROUND_COLOR = Color.WHITE; // 背景色
|
||||
|
||||
/**
|
||||
* 生成营销图片
|
||||
*
|
||||
* @param productImageUrl 商品主图URL
|
||||
* @param originalPrice 官网价
|
||||
* @param finalPrice 到手价
|
||||
* @param productName 商品名称(可选,如果为空则使用AI提取)
|
||||
* @return Base64编码的图片
|
||||
*/
|
||||
public String generateMarketingImage(String productImageUrl, Double originalPrice, Double finalPrice, String productName) {
|
||||
try {
|
||||
log.info("开始生成营销图片: productImageUrl={}, originalPrice={}, finalPrice={}, productName={}",
|
||||
productImageUrl, originalPrice, finalPrice, productName);
|
||||
|
||||
// 1. 加载商品主图
|
||||
BufferedImage productImage = loadProductImage(productImageUrl);
|
||||
if (productImage == null) {
|
||||
throw new IOException("无法加载商品主图: " + productImageUrl);
|
||||
}
|
||||
|
||||
// 2. 提取商品标题关键部分(如果未提供)
|
||||
String keyProductName = productName;
|
||||
if (StrUtil.isBlank(keyProductName)) {
|
||||
// 如果未提供商品名称,则无法提取,留空
|
||||
keyProductName = "";
|
||||
} else {
|
||||
// 如果提供了完整商品名称,提取关键部分
|
||||
keyProductName = extractKeyProductName(keyProductName);
|
||||
}
|
||||
|
||||
// 3. 创建画布
|
||||
BufferedImage canvas = new BufferedImage(OUTPUT_WIDTH, OUTPUT_HEIGHT, BufferedImage.TYPE_INT_RGB);
|
||||
Graphics2D g2d = canvas.createGraphics();
|
||||
|
||||
// 设置抗锯齿
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||
|
||||
// 4. 绘制背景
|
||||
g2d.setColor(BACKGROUND_COLOR);
|
||||
g2d.fillRect(0, 0, OUTPUT_WIDTH, OUTPUT_HEIGHT);
|
||||
|
||||
// 5. 缩放并绘制商品主图(居中,保持比例)
|
||||
int productImageSize = 800; // 商品图尺寸
|
||||
int productImageX = (OUTPUT_WIDTH - productImageSize) / 2;
|
||||
int productImageY = 80; // 顶部留白
|
||||
|
||||
BufferedImage scaledProductImage = Thumbnails.of(productImage)
|
||||
.size(productImageSize, productImageSize)
|
||||
.asBufferedImage();
|
||||
|
||||
g2d.drawImage(scaledProductImage, productImageX, productImageY, null);
|
||||
|
||||
// 6. 绘制商品名称(如果有)
|
||||
int textStartY = productImageY + productImageSize + 40;
|
||||
if (StrUtil.isNotBlank(keyProductName)) {
|
||||
drawProductName(g2d, keyProductName, textStartY);
|
||||
textStartY += 60; // 增加间距
|
||||
}
|
||||
|
||||
// 7. 绘制官网价(带删除线,在上方)
|
||||
int originalPriceY = textStartY + 80;
|
||||
drawOriginalPrice(g2d, originalPrice, originalPriceY);
|
||||
|
||||
// 8. 绘制向下箭头
|
||||
int arrowY = originalPriceY + 60;
|
||||
drawDownArrow(g2d, arrowY);
|
||||
|
||||
// 9. 绘制到手价(大红色,在下方)
|
||||
int finalPriceY = arrowY + 80;
|
||||
drawFinalPrice(g2d, finalPrice, finalPriceY);
|
||||
|
||||
// 10. 绘制爆炸贴图装饰(右下角)
|
||||
drawExplosionDecoration(g2d);
|
||||
|
||||
g2d.dispose();
|
||||
|
||||
// 11. 转换为Base64
|
||||
String base64Image = imageToBase64(canvas, "jpg");
|
||||
log.info("营销图片生成成功");
|
||||
return base64Image;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("生成营销图片失败", e);
|
||||
throw new RuntimeException("生成营销图片失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载商品主图
|
||||
*/
|
||||
private BufferedImage loadProductImage(String imageUrl) throws IOException {
|
||||
try {
|
||||
byte[] imageData = HttpUtil.downloadBytes(imageUrl);
|
||||
if (imageData == null || imageData.length == 0) {
|
||||
throw new IOException("下载图片失败或图片数据为空");
|
||||
}
|
||||
|
||||
try (ByteArrayInputStream bais = new ByteArrayInputStream(imageData)) {
|
||||
return ImageIO.read(bais);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("加载商品主图失败: {}", imageUrl, e);
|
||||
throw new IOException("加载商品主图失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取商品标题关键部分(使用AI)
|
||||
*
|
||||
* @param fullProductName 完整商品名称
|
||||
* @return 提取的关键部分
|
||||
*/
|
||||
public String extractKeyProductName(String fullProductName) {
|
||||
if (StrUtil.isBlank(fullProductName)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
try {
|
||||
// 使用DeepSeek提取商品标题关键部分
|
||||
String prompt = String.format(
|
||||
"请从以下商品标题中提取最关键的3-8个字作为核心卖点,只返回提取的关键词,不要其他内容:\n%s",
|
||||
fullProductName
|
||||
);
|
||||
|
||||
String extracted = deepSeekClientUtil.getDeepSeekResponse(prompt);
|
||||
if (StrUtil.isNotBlank(extracted)) {
|
||||
// 清理可能的换行和多余空格
|
||||
extracted = extracted.trim().replaceAll("\\s+", "");
|
||||
// 限制长度
|
||||
if (extracted.length() > 12) {
|
||||
extracted = extracted.substring(0, 12);
|
||||
}
|
||||
log.info("提取商品标题关键部分成功: {} -> {}", fullProductName, extracted);
|
||||
return extracted;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("使用AI提取商品标题关键部分失败,使用简单截取: {}", fullProductName, e);
|
||||
}
|
||||
|
||||
// 降级方案:简单截取前部分
|
||||
return simpleExtractKeyName(fullProductName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 简单提取商品名称关键部分(降级方案)
|
||||
*/
|
||||
private String simpleExtractKeyName(String fullName) {
|
||||
if (StrUtil.isBlank(fullName)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// 移除常见的规格信息(如XL、175/96A等)
|
||||
String cleaned = fullName
|
||||
.replaceAll("\\s*XL|L|M|S|XXL\\s*", "")
|
||||
.replaceAll("\\s*\\d+/\\d+[A-Z]?\\s*", "")
|
||||
.replaceAll("\\s*【.*?】\\s*", "")
|
||||
.replaceAll("\\s*\\(.*?\\)\\s*", "");
|
||||
|
||||
// 提取前10-15个字符
|
||||
if (cleaned.length() > 15) {
|
||||
cleaned = cleaned.substring(0, 15);
|
||||
}
|
||||
|
||||
return cleaned.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制商品名称
|
||||
*/
|
||||
private void drawProductName(Graphics2D g2d, String productName, int y) {
|
||||
Font font = getAvailableFont(Font.BOLD, PRODUCT_NAME_FONT_SIZE);
|
||||
g2d.setFont(font);
|
||||
g2d.setColor(PRODUCT_NAME_COLOR);
|
||||
|
||||
// 计算文字宽度,如果太长则截断
|
||||
FontMetrics fm = g2d.getFontMetrics();
|
||||
String displayName = productName;
|
||||
int maxWidth = OUTPUT_WIDTH - 80; // 左右各留40px
|
||||
|
||||
if (fm.stringWidth(displayName) > maxWidth) {
|
||||
// 截断并添加省略号
|
||||
while (fm.stringWidth(displayName + "...") > maxWidth && displayName.length() > 0) {
|
||||
displayName = displayName.substring(0, displayName.length() - 1);
|
||||
}
|
||||
displayName += "...";
|
||||
}
|
||||
|
||||
int textWidth = fm.stringWidth(displayName);
|
||||
int x = (OUTPUT_WIDTH - textWidth) / 2; // 居中
|
||||
|
||||
g2d.drawString(displayName, x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制官网价(带删除线)
|
||||
*/
|
||||
private void drawOriginalPrice(Graphics2D g2d, Double originalPrice, int y) {
|
||||
Font font = getAvailableFont(Font.BOLD, ORIGINAL_PRICE_FONT_SIZE);
|
||||
g2d.setFont(font);
|
||||
g2d.setColor(ORIGINAL_PRICE_COLOR);
|
||||
|
||||
String priceText = "官网价:¥" + String.format("%.0f", originalPrice);
|
||||
FontMetrics fm = g2d.getFontMetrics();
|
||||
int textWidth = fm.stringWidth(priceText);
|
||||
int x = (OUTPUT_WIDTH - textWidth) / 2; // 居中
|
||||
|
||||
// 绘制文字
|
||||
g2d.drawString(priceText, x, y);
|
||||
|
||||
// 绘制删除线
|
||||
int lineY = y - fm.getAscent() / 2;
|
||||
g2d.setStroke(new BasicStroke(3.0f)); // 3px粗的删除线
|
||||
g2d.drawLine(x, lineY, x + textWidth, lineY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制向下箭头
|
||||
*/
|
||||
private void drawDownArrow(Graphics2D g2d, int y) {
|
||||
int centerX = OUTPUT_WIDTH / 2;
|
||||
int arrowSize = 40;
|
||||
|
||||
g2d.setColor(new Color(200, 200, 200)); // 浅灰色箭头
|
||||
g2d.setStroke(new BasicStroke(3.0f));
|
||||
|
||||
// 绘制竖线
|
||||
g2d.drawLine(centerX, y, centerX, y + arrowSize);
|
||||
|
||||
// 绘制箭头(向下)
|
||||
int[] xPoints = {centerX, centerX - 15, centerX + 15};
|
||||
int[] yPoints = {y + arrowSize, y + arrowSize - 20, y + arrowSize - 20};
|
||||
g2d.fillPolygon(xPoints, yPoints, 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制到手价(大红色)
|
||||
*/
|
||||
private void drawFinalPrice(Graphics2D g2d, Double finalPrice, int y) {
|
||||
Font font = getAvailableFont(Font.BOLD, FINAL_PRICE_FONT_SIZE);
|
||||
g2d.setFont(font);
|
||||
g2d.setColor(FINAL_PRICE_COLOR);
|
||||
|
||||
String priceText = "到手价:¥" + String.format("%.0f", finalPrice);
|
||||
FontMetrics fm = g2d.getFontMetrics();
|
||||
int textWidth = fm.stringWidth(priceText);
|
||||
int x = (OUTPUT_WIDTH - textWidth) / 2; // 居中
|
||||
|
||||
g2d.drawString(priceText, x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制爆炸贴图装饰(右下角)
|
||||
*/
|
||||
private void drawExplosionDecoration(Graphics2D g2d) {
|
||||
// 绘制简单的爆炸形状(星形)
|
||||
int centerX = OUTPUT_WIDTH - 120;
|
||||
int centerY = OUTPUT_HEIGHT - 120;
|
||||
int radius = 50;
|
||||
|
||||
g2d.setColor(new Color(255, 200, 0)); // 金黄色
|
||||
g2d.setStroke(new BasicStroke(4.0f));
|
||||
|
||||
// 绘制星形爆炸效果
|
||||
int points = 8;
|
||||
int[] xPoints = new int[points * 2];
|
||||
int[] yPoints = new int[points * 2];
|
||||
|
||||
for (int i = 0; i < points * 2; i++) {
|
||||
double angle = Math.PI * i / points;
|
||||
int r = (i % 2 == 0) ? radius : radius / 2;
|
||||
xPoints[i] = (int) (centerX + r * Math.cos(angle));
|
||||
yPoints[i] = (int) (centerY + r * Math.sin(angle));
|
||||
}
|
||||
|
||||
g2d.fillPolygon(xPoints, yPoints, points * 2);
|
||||
|
||||
// 绘制内部圆形
|
||||
g2d.setColor(new Color(255, 100, 0)); // 橙红色
|
||||
g2d.fillOval(centerX - radius / 2, centerY - radius / 2, radius, radius);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将BufferedImage转换为Base64字符串
|
||||
*/
|
||||
private String imageToBase64(BufferedImage image, String format) throws IOException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ImageIO.write(image, format, baos);
|
||||
byte[] imageBytes = baos.toByteArray();
|
||||
return "data:image/" + format + ";base64," + Base64.getEncoder().encodeToString(imageBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量生成营销图片
|
||||
*
|
||||
* @param requests 批量请求列表
|
||||
* @return 结果列表,每个元素包含base64图片
|
||||
*/
|
||||
public Map<String, Object> batchGenerateMarketingImages(java.util.List<Map<String, Object>> requests) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
java.util.List<Map<String, Object>> results = new java.util.ArrayList<>();
|
||||
int successCount = 0;
|
||||
int failCount = 0;
|
||||
|
||||
for (int i = 0; i < requests.size(); i++) {
|
||||
Map<String, Object> request = requests.get(i);
|
||||
Map<String, Object> itemResult = new HashMap<>();
|
||||
|
||||
try {
|
||||
String productImageUrl = (String) request.get("productImageUrl");
|
||||
Double originalPrice = getDoubleValue(request.get("originalPrice"));
|
||||
Double finalPrice = getDoubleValue(request.get("finalPrice"));
|
||||
String productName = (String) request.get("productName");
|
||||
|
||||
if (productImageUrl == null || originalPrice == null || finalPrice == null) {
|
||||
throw new IllegalArgumentException("缺少必要参数: productImageUrl, originalPrice, finalPrice");
|
||||
}
|
||||
|
||||
String base64Image = generateMarketingImage(productImageUrl, originalPrice, finalPrice, productName);
|
||||
|
||||
itemResult.put("success", true);
|
||||
itemResult.put("imageBase64", base64Image);
|
||||
itemResult.put("index", i);
|
||||
successCount++;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("批量生成第{}张图片失败", i, e);
|
||||
itemResult.put("success", false);
|
||||
itemResult.put("error", e.getMessage());
|
||||
itemResult.put("index", i);
|
||||
failCount++;
|
||||
}
|
||||
|
||||
results.add(itemResult);
|
||||
}
|
||||
|
||||
result.put("results", results);
|
||||
result.put("total", requests.size());
|
||||
result.put("successCount", successCount);
|
||||
result.put("failCount", failCount);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全获取Double值
|
||||
*/
|
||||
private Double getDoubleValue(Object value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (value instanceof Double) {
|
||||
return (Double) value;
|
||||
}
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).doubleValue();
|
||||
}
|
||||
try {
|
||||
return Double.parseDouble(value.toString());
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user