生产 Java 未设 AIIMAGE_OSS_BUCKET,默认桶为私有 nanri-ai-images;而 web_config 历史版本全部存于公开 client 桶(oss.aishufu.top/client/nanri-image/versions/..., 桌面端检测更新匿名下载)。原 uploadSoftwareVersionPackage 走默认桶会把新版本写入 私桶导致客户端无法下载。改为显式写 software-version-bucket(新增可配属性,默认 client,env AIIMAGE_OSS_SOFTWARE_VERSION_BUCKET)并保持 nanri-image/versions/ 前缀。 注:本提交含 OssStorageService 上"软件版本上传"方法与早前 readObjectBytesBounded 改动,为软件版本模块(见 ③)的配套存储能力。
This commit is contained in:
@@ -14,6 +14,8 @@ public class OssProperties {
|
|||||||
private String imageVideoBucket;
|
private String imageVideoBucket;
|
||||||
private String digitalHumanBucket;
|
private String digitalHumanBucket;
|
||||||
private String templateBucket;
|
private String templateBucket;
|
||||||
|
/** 桌面客户端软件安装包桶(历史存公开 client 桶,勿用默认私桶)。 */
|
||||||
|
private String softwareVersionBucket;
|
||||||
private String accessKeyId;
|
private String accessKeyId;
|
||||||
private String accessKeySecret;
|
private String accessKeySecret;
|
||||||
}
|
}
|
||||||
|
|||||||
+57
@@ -14,6 +14,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URLDecoder;
|
import java.net.URLDecoder;
|
||||||
@@ -29,6 +30,7 @@ public class OssStorageService {
|
|||||||
|
|
||||||
private static final String IMAGE_VIDEO_MODULE = "IMAGE_VIDEO";
|
private static final String IMAGE_VIDEO_MODULE = "IMAGE_VIDEO";
|
||||||
private static final String DIGITAL_HUMAN_PREFIX = "digital-human/versions/";
|
private static final String DIGITAL_HUMAN_PREFIX = "digital-human/versions/";
|
||||||
|
private static final String SOFTWARE_VERSION_PREFIX = "nanri-image/versions/";
|
||||||
private static final String LEGACY_MINIO_ENDPOINT = "http://47.110.241.161:9000";
|
private static final String LEGACY_MINIO_ENDPOINT = "http://47.110.241.161:9000";
|
||||||
|
|
||||||
private final OssProperties ossProperties;
|
private final OssProperties ossProperties;
|
||||||
@@ -77,6 +79,26 @@ public class OssStorageService {
|
|||||||
return objectKey;
|
return objectKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传桌面客户端软件安装包(web_config 关联的 zip):写入**公开 client 桶**的
|
||||||
|
* {@code nanri-image/versions/} 前缀(对齐 Flask 历史对象布局与公开直链,
|
||||||
|
* 勿用默认私有桶 nanri-ai-images,否则桌面端"检测更新"无法匿名下载)。
|
||||||
|
* 桶可用 aiimage.oss.software-version-bucket(env AIIMAGE_OSS_SOFTWARE_VERSION_BUCKET)覆盖。
|
||||||
|
*/
|
||||||
|
public String uploadSoftwareVersionPackage(File file, String objectKey) {
|
||||||
|
if (objectKey == null || !objectKey.startsWith(SOFTWARE_VERSION_PREFIX)) {
|
||||||
|
throw new IllegalArgumentException("software version objectKey must start with " + SOFTWARE_VERSION_PREFIX);
|
||||||
|
}
|
||||||
|
String bucket = softwareVersionBucket();
|
||||||
|
uploadFile(file, bucket, objectKey);
|
||||||
|
return getPublicUrl(objectKey, bucket);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String softwareVersionBucket() {
|
||||||
|
String configured = ossProperties.getSoftwareVersionBucket();
|
||||||
|
return (configured == null || configured.isBlank()) ? "client" : configured;
|
||||||
|
}
|
||||||
|
|
||||||
public String uploadText(String objectKey, String content) {
|
public String uploadText(String objectKey, String content) {
|
||||||
if (objectKey == null || objectKey.isBlank()) {
|
if (objectKey == null || objectKey.isBlank()) {
|
||||||
throw new IllegalArgumentException("objectKey must not be blank");
|
throw new IllegalArgumentException("objectKey must not be blank");
|
||||||
@@ -159,6 +181,41 @@ public class OssStorageService {
|
|||||||
return readObjectBytes(location.bucket(), location.objectKey());
|
return readObjectBytes(location.bucket(), location.objectKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a managed result object with a hard cap on the total bytes read.
|
||||||
|
* Streaming 1 MiB chunks, aborting once {@code maxBytes} is exceeded.
|
||||||
|
* The caller is expected to translate the value result_file_size.
|
||||||
|
*/
|
||||||
|
public byte[] readObjectBytesBounded(String value, long maxBytes) {
|
||||||
|
StorageLocation location = resolveStorageLocation(value);
|
||||||
|
if (location == null) {
|
||||||
|
throw new IllegalArgumentException("object value must not be blank");
|
||||||
|
}
|
||||||
|
String bucket = location.bucket();
|
||||||
|
String objectKey = location.objectKey();
|
||||||
|
try (var stream = buildClient().getObject(GetObjectArgs.builder()
|
||||||
|
.bucket(bucket)
|
||||||
|
.object(objectKey)
|
||||||
|
.build())) {
|
||||||
|
byte[] buffer = new byte[1024 * 1024];
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
long total = 0;
|
||||||
|
int read;
|
||||||
|
while ((read = stream.read(buffer)) != -1) {
|
||||||
|
total += read;
|
||||||
|
if (total > maxBytes) {
|
||||||
|
throw new IllegalArgumentException("结果文件过大,无法分析");
|
||||||
|
}
|
||||||
|
out.write(buffer, 0, read);
|
||||||
|
}
|
||||||
|
return out.toByteArray();
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
throw ex;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw storageFailure("read", objectKey, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public boolean objectExists(String bucket, String objectKey) {
|
public boolean objectExists(String bucket, String objectKey) {
|
||||||
String normalizedBucket = requireStorageName(bucket, "bucket");
|
String normalizedBucket = requireStorageName(bucket, "bucket");
|
||||||
String normalizedObjectKey = requireStorageName(objectKey, "objectKey");
|
String normalizedObjectKey = requireStorageName(objectKey, "objectKey");
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ aiimage:
|
|||||||
image-video-bucket: ${AIIMAGE_IMAGE_VIDEO_OSS_BUCKET:shufu-video}
|
image-video-bucket: ${AIIMAGE_IMAGE_VIDEO_OSS_BUCKET:shufu-video}
|
||||||
digital-human-bucket: ${AIIMAGE_DIGITAL_HUMAN_OSS_BUCKET:nanri-ai-digital-human}
|
digital-human-bucket: ${AIIMAGE_DIGITAL_HUMAN_OSS_BUCKET:nanri-ai-digital-human}
|
||||||
template-bucket: ${AIIMAGE_TEMPLATE_OSS_BUCKET:aiimage-templates}
|
template-bucket: ${AIIMAGE_TEMPLATE_OSS_BUCKET:aiimage-templates}
|
||||||
|
software-version-bucket: ${AIIMAGE_OSS_SOFTWARE_VERSION_BUCKET:client}
|
||||||
access-key-id: ${AIIMAGE_OSS_ACCESS_KEY_ID:appuser}
|
access-key-id: ${AIIMAGE_OSS_ACCESS_KEY_ID:appuser}
|
||||||
access-key-secret: ${AIIMAGE_OSS_ACCESS_KEY_SECRET:AppUser@2024SecureKey}
|
access-key-secret: ${AIIMAGE_OSS_ACCESS_KEY_SECRET:AppUser@2024SecureKey}
|
||||||
transient-storage:
|
transient-storage:
|
||||||
|
|||||||
Reference in New Issue
Block a user