diff --git a/open-isle-cli/src/utils/vditor.js b/open-isle-cli/src/utils/vditor.js
index 5592d017b..2e9a2628c 100644
--- a/open-isle-cli/src/utils/vditor.js
+++ b/open-isle-cli/src/utils/vditor.js
@@ -37,7 +37,8 @@ export function createVditor(editorId, options = {}) {
return searchUsers(value)
}
- return new Vditor(editorId, {
+ let vditor
+ vditor = new Vditor(editorId, {
placeholder,
height: 'auto',
theme: getEditorTheme(),
@@ -76,34 +77,91 @@ export function createVditor(editorId, options = {}) {
'upload'
],
upload: {
- fieldName: 'file',
- url: `${API_BASE_URL}/api/upload`,
accept: 'image/*,video/*',
multiple: false,
- headers: { Authorization: `Bearer ${getToken()}` },
- format(files, responseText) {
- const res = JSON.parse(responseText)
- if (res.code === 0) {
- return JSON.stringify({
- code: 0,
- msg: '',
- data: {
- errFiles: [],
- succMap: { [files[0].name]: res.data.url }
- }
- })
- } else {
- return JSON.stringify({
- code: 1,
- msg: '上传失败',
- data: { errFiles: files.map(f => f.name), succMap: {} }
- })
+ handler: async (files) => {
+ const file = files[0]
+ vditor.tip('图片上传中', 0)
+ vditor.disabled()
+ const res = await fetch(
+ `${API_BASE_URL}/api/upload/presign?filename=${encodeURIComponent(file.name)}`,
+ { headers: { Authorization: `Bearer ${getToken()}` } }
+ )
+ if (!res.ok) {
+ vditor.enable()
+ vditor.tip('获取上传地址失败')
+ return '获取上传地址失败'
}
+ const info = await res.json()
+ const put = await fetch(info.uploadUrl, { method: 'PUT', body: file })
+ if (!put.ok) {
+ vditor.enable()
+ vditor.tip('上传失败')
+ return '上传失败'
+ }
+
+ const ext = file.name.split('.').pop().toLowerCase()
+ const imageExts = [
+ 'apng',
+ 'bmp',
+ 'gif',
+ 'ico',
+ 'cur',
+ 'jpg',
+ 'jpeg',
+ 'jfif',
+ 'pjp',
+ 'pjpeg',
+ 'png',
+ 'svg',
+ 'webp'
+ ]
+ const audioExts = ['wav', 'mp3', 'ogg']
+ let md
+ if (imageExts.includes(ext)) {
+ md = ``
+ } else if (audioExts.includes(ext)) {
+ md = ``
+ } else {
+ md = `[${file.name}](${info.fileUrl})`
+ }
+ vditor.insertValue(md + '\n')
+ vditor.enable()
+ vditor.tip('上传成功')
+ return null
}
},
+ // upload: {
+ // fieldName: 'file',
+ // url: `${API_BASE_URL}/api/upload`,
+ // accept: 'image/*,video/*',
+ // multiple: false,
+ // headers: { Authorization: `Bearer ${getToken()}` },
+ // format(files, responseText) {
+ // const res = JSON.parse(responseText)
+ // if (res.code === 0) {
+ // return JSON.stringify({
+ // code: 0,
+ // msg: '',
+ // data: {
+ // errFiles: [],
+ // succMap: { [files[0].name]: res.data.url }
+ // }
+ // })
+ // } else {
+ // return JSON.stringify({
+ // code: 1,
+ // msg: '上传失败',
+ // data: { errFiles: files.map(f => f.name), succMap: {} }
+ // })
+ // }
+ // }
+ // },
toolbarConfig: { pin: true },
cache: { enable: false },
input,
after
})
+
+ return vditor
}
diff --git a/src/main/java/com/openisle/controller/UploadController.java b/src/main/java/com/openisle/controller/UploadController.java
index b21c5432c..fe27b2917 100644
--- a/src/main/java/com/openisle/controller/UploadController.java
+++ b/src/main/java/com/openisle/controller/UploadController.java
@@ -74,4 +74,9 @@ public class UploadController {
return ResponseEntity.internalServerError().body(Map.of("code", 3, "msg", "Upload failed"));
}
}
+
+ @GetMapping("/presign")
+ public java.util.Map presign(@RequestParam("filename") String filename) {
+ return imageUploader.presignUpload(filename);
+ }
}
diff --git a/src/main/java/com/openisle/service/CosImageUploader.java b/src/main/java/com/openisle/service/CosImageUploader.java
index accf028f6..4848daa49 100644
--- a/src/main/java/com/openisle/service/CosImageUploader.java
+++ b/src/main/java/com/openisle/service/CosImageUploader.java
@@ -6,6 +6,8 @@ import com.qcloud.cos.auth.BasicCOSCredentials;
import com.qcloud.cos.auth.COSCredentials;
import com.qcloud.cos.model.ObjectMetadata;
import com.qcloud.cos.model.PutObjectRequest;
+import com.qcloud.cos.http.HttpMethodName;
+import com.qcloud.cos.model.GeneratePresignedUrlRequest;
import com.qcloud.cos.region.Region;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
@@ -98,4 +100,25 @@ public class CosImageUploader extends ImageUploader {
logger.warn("Failed to delete image {} from COS", key, e);
}
}
+
+ @Override
+ public java.util.Map presignUpload(String filename) {
+ String ext = "";
+ int dot = filename.lastIndexOf('.');
+ if (dot != -1) {
+ ext = filename.substring(dot);
+ }
+ String randomName = java.util.UUID.randomUUID().toString().replace("-", "") + ext;
+ String objectKey = UPLOAD_DIR + randomName;
+ java.util.Date expiration = new java.util.Date(System.currentTimeMillis() + 15 * 60 * 1000L);
+ GeneratePresignedUrlRequest req = new GeneratePresignedUrlRequest(bucketName, objectKey, HttpMethodName.PUT);
+ req.setExpiration(expiration);
+ java.net.URL url = cosClient.generatePresignedUrl(req);
+ String fileUrl = baseUrl + "/" + objectKey;
+ return java.util.Map.of(
+ "uploadUrl", url.toString(),
+ "fileUrl", fileUrl,
+ "key", objectKey
+ );
+ }
}
diff --git a/src/main/java/com/openisle/service/ImageUploader.java b/src/main/java/com/openisle/service/ImageUploader.java
index 04abf1585..e7a84b6de 100644
--- a/src/main/java/com/openisle/service/ImageUploader.java
+++ b/src/main/java/com/openisle/service/ImageUploader.java
@@ -38,6 +38,14 @@ public abstract class ImageUploader {
protected abstract void deleteFromStore(String key);
+ /**
+ * Generate a presigned PUT URL for direct browser upload.
+ * Default implementation is unsupported.
+ */
+ public java.util.Map presignUpload(String filename) {
+ throw new UnsupportedOperationException("presignUpload not supported");
+ }
+
/** Extract COS URLs from text. */
public Set extractUrls(String text) {
Set set = new HashSet<>();