mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-08-10 13:21:00 +00:00
Merge pull request #294 from nagisa77/codex/modify-image-upload-to-frontend-direct-transfer
Enable presigned URL uploads
This commit is contained in:
@@ -37,7 +37,8 @@ export function createVditor(editorId, options = {}) {
|
|||||||
return searchUsers(value)
|
return searchUsers(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Vditor(editorId, {
|
let vditor
|
||||||
|
vditor = new Vditor(editorId, {
|
||||||
placeholder,
|
placeholder,
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
theme: getEditorTheme(),
|
theme: getEditorTheme(),
|
||||||
@@ -76,34 +77,91 @@ export function createVditor(editorId, options = {}) {
|
|||||||
'upload'
|
'upload'
|
||||||
],
|
],
|
||||||
upload: {
|
upload: {
|
||||||
fieldName: 'file',
|
|
||||||
url: `${API_BASE_URL}/api/upload`,
|
|
||||||
accept: 'image/*,video/*',
|
accept: 'image/*,video/*',
|
||||||
multiple: false,
|
multiple: false,
|
||||||
headers: { Authorization: `Bearer ${getToken()}` },
|
handler: async (files) => {
|
||||||
format(files, responseText) {
|
const file = files[0]
|
||||||
const res = JSON.parse(responseText)
|
vditor.tip('图片上传中', 0)
|
||||||
if (res.code === 0) {
|
vditor.disabled()
|
||||||
return JSON.stringify({
|
const res = await fetch(
|
||||||
code: 0,
|
`${API_BASE_URL}/api/upload/presign?filename=${encodeURIComponent(file.name)}`,
|
||||||
msg: '',
|
{ headers: { Authorization: `Bearer ${getToken()}` } }
|
||||||
data: {
|
)
|
||||||
errFiles: [],
|
if (!res.ok) {
|
||||||
succMap: { [files[0].name]: res.data.url }
|
vditor.enable()
|
||||||
}
|
vditor.tip('获取上传地址失败')
|
||||||
})
|
return '获取上传地址失败'
|
||||||
} else {
|
|
||||||
return JSON.stringify({
|
|
||||||
code: 1,
|
|
||||||
msg: '上传失败',
|
|
||||||
data: { errFiles: files.map(f => f.name), succMap: {} }
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
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 = `<audio controls="controls" src="${info.fileUrl}"></audio>`
|
||||||
|
} 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 },
|
toolbarConfig: { pin: true },
|
||||||
cache: { enable: false },
|
cache: { enable: false },
|
||||||
input,
|
input,
|
||||||
after
|
after
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return vditor
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,4 +74,9 @@ public class UploadController {
|
|||||||
return ResponseEntity.internalServerError().body(Map.of("code", 3, "msg", "Upload failed"));
|
return ResponseEntity.internalServerError().body(Map.of("code", 3, "msg", "Upload failed"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/presign")
|
||||||
|
public java.util.Map<String, String> presign(@RequestParam("filename") String filename) {
|
||||||
|
return imageUploader.presignUpload(filename);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import com.qcloud.cos.auth.BasicCOSCredentials;
|
|||||||
import com.qcloud.cos.auth.COSCredentials;
|
import com.qcloud.cos.auth.COSCredentials;
|
||||||
import com.qcloud.cos.model.ObjectMetadata;
|
import com.qcloud.cos.model.ObjectMetadata;
|
||||||
import com.qcloud.cos.model.PutObjectRequest;
|
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 com.qcloud.cos.region.Region;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
|
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);
|
logger.warn("Failed to delete image {} from COS", key, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public java.util.Map<String, String> 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
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,14 @@ public abstract class ImageUploader {
|
|||||||
|
|
||||||
protected abstract void deleteFromStore(String key);
|
protected abstract void deleteFromStore(String key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a presigned PUT URL for direct browser upload.
|
||||||
|
* Default implementation is unsupported.
|
||||||
|
*/
|
||||||
|
public java.util.Map<String, String> presignUpload(String filename) {
|
||||||
|
throw new UnsupportedOperationException("presignUpload not supported");
|
||||||
|
}
|
||||||
|
|
||||||
/** Extract COS URLs from text. */
|
/** Extract COS URLs from text. */
|
||||||
public Set<String> extractUrls(String text) {
|
public Set<String> extractUrls(String text) {
|
||||||
Set<String> set = new HashSet<>();
|
Set<String> set = new HashSet<>();
|
||||||
|
|||||||
Reference in New Issue
Block a user