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 = `![${file.name}](${info.fileUrl})` + } 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<>();