@@ -20,6 +20,7 @@ import com.rj.entity.ImageModel;
import com.rj.mapper.ImageModelMapper ;
import com.rj.service.IImageModelService ;
import com.rj.service.MinIOService ;
import com.rj.config.AliyunConfig ;
import lombok.extern.slf4j.Slf4j ;
import org.springframework.beans.factory.annotation.Autowired ;
import org.springframework.stereotype.Service ;
@@ -27,6 +28,7 @@ import org.springframework.web.multipart.MultipartFile;
import com.fasterxml.jackson.databind.ObjectMapper ;
import java.io.* ;
import java.net.HttpURLConnection ;
import java.net.URL ;
import java.time.LocalDateTime ;
import java.util.* ;
@@ -59,6 +61,9 @@ public class ImageModelServiceImpl extends ServiceImpl<ImageModelMapper, ImageMo
@Autowired
private ObjectMapper objectMapper ;
@Autowired
private AliyunConfig aliyunConfig ;
/**
* 保存图像模型记录
@@ -870,7 +875,7 @@ public class ImageModelServiceImpl extends ServiceImpl<ImageModelMapper, ImageMo
/**
* 下载图片到字节数组
*
* 从指定URL下载图片并返回字节数组
* 从指定URL下载图片并返回字节数组,支持超时配置
*
* @param imageUrl 图片URL
* @return 图片字节数组
@@ -881,19 +886,36 @@ public class ImageModelServiceImpl extends ServiceImpl<ImageModelMapper, ImageMo
log . info ( " 开始下载图片: {} " , imageUrl ) ;
URL url = new URL ( imageUrl ) ;
try ( InputStream inputStream = url . openStream ( ) ;
ByteArrayOutputStream outputStream = new ByteArrayOutputStream ( ) ) {
byte [ ] buffer = new byte [ 4096 ] ;
int bytesRead ;
while ( ( bytesRead = inputStream . read ( buffer ) ) ! = - 1 ) {
outputStream . write ( buffer , 0 , bytesRead ) ;
HttpURLConnection connection = ( HttpURLConnection ) url . openConnection ( ) ;
// 设置请求头
connection . setRequestMethod ( " GET " ) ;
connection . setConnectTimeout ( aliyunConfig . getApi ( ) . getConnectTimeout ( ) ) ; // 使用配置的连接超时
connection . setReadTimeout ( aliyunConfig . getApi ( ) . getReadTimeout ( ) ) ; // 使用配置的读取超时
connection . setRequestProperty ( " User-Agent " , " Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " ) ;
connection . setRequestProperty ( " Accept " , " image/* " ) ;
connection . setRequestProperty ( " Accept-Encoding " , " identity " ) ; // 禁用压缩
int responseCode = connection . getResponseCode ( ) ;
if ( responseCode = = HttpURLConnection . HTTP_OK ) {
try ( InputStream inputStream = connection . getInputStream ( ) ;
ByteArrayOutputStream outputStream = new ByteArrayOutputStream ( ) ) {
byte [ ] buffer = new byte [ 8192 ] ; // 增大缓冲区
int bytesRead ;
while ( ( bytesRead = inputStream . read ( buffer ) ) ! = - 1 ) {
outputStream . write ( buffer , 0 , bytesRead ) ;
}
byte [ ] imageBytes = outputStream . toByteArray ( ) ;
log . info ( " 图片下载完成,大小: {} bytes " , imageBytes . length ) ;
return imageBytes ;
}
byte [ ] imageBytes = outputStream . toByteArray ( ) ;
log . info ( " 图片下载完成,大小: {} bytes " , imageBytes . length ) ;
return imageBytes ;
} else {
log . error ( " 下载图片失败, HTTP状态码: {}, URL: {} " , responseCode , imageUrl ) ;
throw new IOException ( " HTTP error code: " + responseCode ) ;
}
} catch ( Exception e ) {
log . error ( " 下载图片失败: {} " , imageUrl , e ) ;
throw new IOException ( " 下载图片失败: " + e . getMessage ( ) , e ) ;
@@ -1128,20 +1150,46 @@ public class ImageModelServiceImpl extends ServiceImpl<ImageModelMapper, ImageMo
// 2. 上传文件到MinIO并生成临时访问URL
log . info ( " 开始上传文件到MinIO, 文件名: {} " , multipartFile . getOriginalFilename ( ) ) ;
log . info ( " 文件大小: {} bytes " , multipartFile . getSize ( ) ) ;
log . info ( " 文件类型: {} " , multipartFile . getContentType ( ) ) ;
// 生成唯一文件名
String originalFilename = multipartFile . getOriginalFilename ( ) ;
String fileExtension = getFileExtension ( originalFilename ) ;
String uniqueFileName = generateUniqueFileName ( fileExtension ) ;
log . info ( " 生成的文件名: {} " , uniqueFileName ) ;
log . info ( " 文件扩展名: {} " , fileExtension ) ;
// 上传到MinIO
long uploadStartTime = System . currentTimeMillis ( ) ;
String materialUrl = minIOService . uploadFileWithName ( multipartFile , uniqueFileName ) ;
long uploadEndTime = System . currentTimeMillis ( ) ;
log . info ( " MinIO上传耗时: {} ms " , uploadEndTime - uploadStartTime ) ;
String materialTempUrl = minIOService . generateTempUrl ( uniqueFileName ) ;
log . info ( " 文件上传到MinIO成功: " ) ;
log . info ( " 永久URL: {} " , materialUrl ) ;
log . info ( " 临时访问URL: {} " , materialTempUrl ) ;
// 验证临时URL是否可访问
log . info ( " 开始验证临时URL可访问性... " ) ;
try {
URL testUrl = new URL ( materialTempUrl ) ;
HttpURLConnection testConnection = ( HttpURLConnection ) testUrl . openConnection ( ) ;
testConnection . setRequestMethod ( " HEAD " ) ;
testConnection . setConnectTimeout ( 10000 ) ;
testConnection . setReadTimeout ( 10000 ) ;
int testResponseCode = testConnection . getResponseCode ( ) ;
log . info ( " 临时URL访问测试结果: HTTP {} " , testResponseCode ) ;
if ( testResponseCode ! = 200 ) {
log . warn ( " 临时URL可能无法访问, 这可能导致阿里云API超时 " ) ;
}
} catch ( Exception e ) {
log . error ( " 临时URL访问测试失败: {} " , e . getMessage ( ) ) ;
}
// 输出到控制台
System . out . println ( " === 图生图文件上传信息 === " ) ;
System . out . println ( " 原始文件名: " + originalFilename ) ;
@@ -1177,11 +1225,38 @@ public class ImageModelServiceImpl extends ServiceImpl<ImageModelMapper, ImageMo
// 5. 使用MultiModalConversation调用阿里云API
MultiModalConversation conv = new MultiModalConversation ( ) ;
// 构建多模态消息
// 构建多模态消息 - 智能选择图片传递方式
Object imageContent ;
// 首先尝试使用临时URL
if ( isUrlAccessible ( materialTempUrl ) ) {
log . info ( " 使用临时URL传递图片: {} " , materialTempUrl ) ;
imageContent = materialTempUrl ;
}
// 如果临时URL不可访问, 尝试永久URL
else if ( isUrlAccessible ( materialUrl ) ) {
log . warn ( " 临时URL不可访问, 使用永久URL: {} " , materialUrl ) ;
imageContent = materialUrl ;
}
// 如果URL都不可访问, 使用base64编码
else {
log . warn ( " 所有URL都不可访问, 尝试使用base64编码传递图片 " ) ;
try {
byte [ ] imageBytes = multipartFile . getBytes ( ) ;
String base64Image = " data:image/ " + getFileExtension ( multipartFile . getOriginalFilename ( ) ) + " ;base64, " +
java . util . Base64 . getEncoder ( ) . encodeToString ( imageBytes ) ;
imageContent = base64Image ;
log . info ( " 使用base64编码传递图片, 大小: {} bytes " , imageBytes . length ) ;
} catch ( Exception e ) {
log . error ( " 转换为base64失败, 回退到使用临时URL: {} " , e . getMessage ( ) ) ;
imageContent = materialTempUrl ;
}
}
MultiModalMessage userMessage = MultiModalMessage . builder ( )
. role ( Role . USER . getValue ( ) )
. content ( Arrays . asList (
Collections . singletonMap ( " image " , materialTempUrl ) , // 使用临时访问URL
Collections . singletonMap ( " image " , i mageContent ) ,
Collections . singletonMap ( " text " , prompt )
) ) . build ( ) ;
@@ -1219,9 +1294,57 @@ public class ImageModelServiceImpl extends ServiceImpl<ImageModelMapper, ImageMo
System . out . println ( " 参数: " + JsonUtils . toJson ( parametersMap ) ) ;
System . out . println ( " ========================= " ) ;
// 6. 调用阿里云API
// 6. 调用阿里云API(带重试机制)
log . info ( " 开始调用阿里云图生图API " ) ;
MultiModalConversationResult conversationResult = conv . call ( param ) ;
log . info ( " API配置 - 连接超时: {} ms, 读取超时: {} ms " ,
aliyunConfig . getApi ( ) . getConnectTimeout ( ) ,
aliyunConfig . getApi ( ) . getReadTimeout ( ) ) ;
log . info ( " 重试配置 - 最大重试次数: {}, 重试延迟: {} ms " ,
aliyunConfig . getApi ( ) . getMaxRetries ( ) ,
aliyunConfig . getApi ( ) . getRetryDelay ( ) ) ;
MultiModalConversationResult conversationResult = null ;
int maxRetries = aliyunConfig . getApi ( ) . getMaxRetries ( ) ;
int retryCount = 0 ;
while ( retryCount < maxRetries ) {
try {
log . info ( " 第{}次尝试调用阿里云API... " , retryCount + 1 ) ;
long apiStartTime = System . currentTimeMillis ( ) ;
conversationResult = conv . call ( param ) ;
long apiEndTime = System . currentTimeMillis ( ) ;
log . info ( " 阿里云API调用成功, 耗时: {} ms " , apiEndTime - apiStartTime ) ;
break ; // 成功则跳出循环
} catch ( ApiException e ) {
retryCount + + ;
log . error ( " 阿里云API调用失败, 第{}次尝试,错误详情: " , retryCount ) ;
log . error ( " 错误代码: {} " , e . getMessage ( ) ) ;
log . error ( " 请求ID: {} " , extractRequestId ( e . getMessage ( ) ) ) ;
if ( e . getMessage ( ) . contains ( " DataInspection " ) | | e . getMessage ( ) . contains ( " timeout " ) ) {
if ( retryCount < maxRetries ) {
long delayTime = aliyunConfig . getApi ( ) . getRetryDelay ( ) * retryCount ;
log . warn ( " 检测到超时错误,{}ms后进行第{}次重试 " , delayTime , retryCount + 1 ) ;
try {
Thread . sleep ( delayTime ) ;
} catch ( InterruptedException ie ) {
Thread . currentThread ( ) . interrupt ( ) ;
throw new RuntimeException ( " 重试被中断 " , ie ) ;
}
} else {
log . error ( " 阿里云API调用失败, 已达到最大重试次数: {} " , maxRetries ) ;
log . error ( " 最终失败原因: {} " , e . getMessage ( ) ) ;
throw e ;
}
} else {
log . error ( " 非超时错误,直接抛出异常: {} " , e . getMessage ( ) ) ;
throw e ;
}
}
}
// 输出完整响应到控制台
System . out . println ( " === 阿里云API完整响应 === " ) ;
@@ -1358,4 +1481,49 @@ public class ImageModelServiceImpl extends ServiceImpl<ImageModelMapper, ImageMo
return " req_ " + System . currentTimeMillis ( ) + " _ " + UUID . randomUUID ( ) . toString ( ) . substring ( 0 , 8 ) ;
}
/**
* 从错误消息中提取请求ID
*
* @param errorMessage 错误消息
* @return 请求ID, 如果未找到则返回"未知"
*/
private String extractRequestId ( String errorMessage ) {
try {
if ( errorMessage ! = null & & errorMessage . contains ( " requestId " ) ) {
int startIndex = errorMessage . indexOf ( " \" requestId \" : \" " ) + 13 ;
int endIndex = errorMessage . indexOf ( " \" " , startIndex ) ;
if ( startIndex > 12 & & endIndex > startIndex ) {
return errorMessage . substring ( startIndex , endIndex ) ;
}
}
} catch ( Exception e ) {
log . warn ( " 提取请求ID失败: {} " , e . getMessage ( ) ) ;
}
return " 未知 " ;
}
/**
* 检查URL是否可访问
*
* @param urlString URL字符串
* @return 如果URL可访问返回true, 否则返回false
*/
private boolean isUrlAccessible ( String urlString ) {
try {
URL url = new URL ( urlString ) ;
HttpURLConnection connection = ( HttpURLConnection ) url . openConnection ( ) ;
connection . setRequestMethod ( " HEAD " ) ;
connection . setConnectTimeout ( 5000 ) ; // 5秒连接超时
connection . setReadTimeout ( 5000 ) ; // 5秒读取超时
connection . setRequestProperty ( " User-Agent " , " Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " ) ;
int responseCode = connection . getResponseCode ( ) ;
log . info ( " URL访问测试: {} -> HTTP {} " , urlString , responseCode ) ;
return responseCode = = 200 ;
} catch ( Exception e ) {
log . warn ( " URL访问测试失败: {} -> {} " , urlString , e . getMessage ( ) ) ;
return false ;
}
}
}