commit
This commit is contained in:
@@ -2,7 +2,6 @@ package com.budwk.app.sys.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckLogin;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import com.budwk.app.base.annotation.SLog;
|
||||
import com.budwk.app.base.constant.RedisConstant;
|
||||
import com.budwk.app.base.result.Result;
|
||||
@@ -10,6 +9,7 @@ import com.budwk.app.sys.enums.SysFileEngineTypeEnum;
|
||||
import com.budwk.app.sys.services.SysFileService;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import io.swagger.annotations.Api;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.nutz.integration.jedis.RedisService;
|
||||
import org.nutz.integration.jedis.pubsub.PubSubService;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
@@ -22,10 +22,13 @@ import org.nutz.mvc.upload.UploadAdaptor;
|
||||
import redis.clients.jedis.ScanParams;
|
||||
import redis.clients.jedis.ScanResult;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@IocBean
|
||||
@At("/platform/sys/h5ScanCodeUploadFile")
|
||||
@Ok("json:full")
|
||||
@Api(tags = "h5扫码上传文件")
|
||||
@Slf4j
|
||||
public class SysH5ScanCodeUploadFileController {
|
||||
|
||||
@Inject
|
||||
@@ -55,6 +58,8 @@ public class SysH5ScanCodeUploadFileController {
|
||||
String url = sysFileService.uploadReturnUrl(SysFileEngineTypeEnum.MINIO.getValue(), file);
|
||||
return Result.success().addData(url);
|
||||
} catch (Exception e) {
|
||||
log.error("h5扫码上传文件失败,loginName={}, fileName={}, fileSize={}",
|
||||
getCurrentLoginName(), getUploadFileName(file), getUploadFileSize(file), e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -63,26 +68,59 @@ public class SysH5ScanCodeUploadFileController {
|
||||
@At
|
||||
@SLog(tag = "文件管理", msg = "同步到PC")
|
||||
@SaCheckLogin
|
||||
public Result syncPc(@Param("files") JSONObject[] jsonObject, @Param("timestamp") String timestamp) {
|
||||
public Result syncPc(@Param("files") String files, @Param("timestamp") String timestamp) {
|
||||
try {
|
||||
if(ObjectUtil.isEmpty(jsonObject)){
|
||||
if(ObjectUtil.isEmpty(files)){
|
||||
return Result.error("请选择要同步的文件");
|
||||
}
|
||||
if(ObjectUtil.isEmpty(timestamp)){
|
||||
return Result.error("二维码已失效,请刷新电脑端二维码后重试");
|
||||
}
|
||||
|
||||
// 手机端以表单字符串提交附件 JSON,后端显式解析,避免参数自动转换失败导致 500。
|
||||
List<NutMap> fileList = Json.fromJsonAsList(NutMap.class, files);
|
||||
if(ObjectUtil.isEmpty(fileList)){
|
||||
return Result.error("请选择要同步的文件");
|
||||
}
|
||||
|
||||
ScanParams match = new ScanParams().match(RedisConstant.REDIS_KEY_WSROOM + SecurityUtil.getUserLoginname() + ":*");
|
||||
ScanResult<String> scan = null;
|
||||
int sendCount = 0;
|
||||
do {
|
||||
scan = redisService.scan(scan == null ? ScanParams.SCAN_POINTER_START : scan.getStringCursor(), match);
|
||||
for (String key : scan.getResult()) {
|
||||
NutMap data = NutMap.NEW().addv("action", "h5-scan-code-upload-file-" + timestamp).addv("files", jsonObject);
|
||||
NutMap data = NutMap.NEW().addv("action", "h5-scan-code-upload-file-" + timestamp).addv("files", fileList);
|
||||
pubSubService.fire(key, Json.toJson(data));
|
||||
sendCount++;
|
||||
}
|
||||
} while (!scan.isCompleteIteration());
|
||||
|
||||
if(sendCount == 0){
|
||||
return Result.error("未找到电脑端上传窗口,请保持电脑端二维码页面打开");
|
||||
}
|
||||
|
||||
return Result.success().addData(null);
|
||||
} catch (Exception e) {
|
||||
log.error("h5扫码上传文件同步到PC失败,loginName={}, timestamp={}, filesLength={}",
|
||||
getCurrentLoginName(), timestamp, ObjectUtil.isEmpty(files) ? 0 : files.length(), e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private String getCurrentLoginName() {
|
||||
try {
|
||||
return SecurityUtil.getUserLoginname();
|
||||
} catch (Exception e) {
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
private String getUploadFileName(TempFile file) {
|
||||
return file == null ? "" : file.getSubmittedFileName();
|
||||
}
|
||||
|
||||
private long getUploadFileSize(TempFile file) {
|
||||
return file == null ? 0 : file.getSize();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
:action="action"
|
||||
:on-change="handleChange"
|
||||
:on-success="handleSuccess"
|
||||
:on-error="handleError"
|
||||
:on-preview="handlePreview"
|
||||
:on-exceed="handleExceed"
|
||||
:before-upload="beforeUpload"
|
||||
@@ -26,6 +27,7 @@
|
||||
:action="action"
|
||||
:on-change="handleChange"
|
||||
:on-success="handleSuccess"
|
||||
:on-error="handleError"
|
||||
:on-preview="handlePreview"
|
||||
:on-remove="handleRemove"
|
||||
:before-upload="beforeUpload"
|
||||
@@ -67,6 +69,7 @@
|
||||
:action="action"
|
||||
:on-change="handleChange"
|
||||
:on-success="handleSuccess"
|
||||
:on-error="handleError"
|
||||
:on-preview="handlePreview"
|
||||
:on-remove="handleRemove"
|
||||
:on-exceed="handleExceed"
|
||||
@@ -205,15 +208,15 @@ module.exports = {
|
||||
|
||||
let tips = null
|
||||
if (uploadNumber) {
|
||||
tips = `只能上传<span style="color: red">${uploadNumber}</span>个文件;`
|
||||
tips = '只能上传<span style="color: red">' + uploadNumber + '</span>个文件;'
|
||||
}
|
||||
if (fileAccept) {
|
||||
tips = tips + `只能上传<span style="color: red">${fileAccept}</span>文件;`
|
||||
tips = tips + '只能上传<span style="color: red">' + fileAccept + '</span>文件;'
|
||||
} else {
|
||||
tips = tips + '不限格式;'
|
||||
}
|
||||
if (fileSize) {
|
||||
tips = tips + `单个文件大小不能超过<span style="color: red">${(fileSize / 1024 / 1024).toFixed(2)}</span>M;`
|
||||
tips = tips + '单个文件大小不能超过<span style="color: red">' + (fileSize / 1024 / 1024).toFixed(2) + '</span>M;'
|
||||
}
|
||||
return tips
|
||||
},
|
||||
@@ -341,7 +344,7 @@ module.exports = {
|
||||
if (valid) {
|
||||
return true
|
||||
}
|
||||
this.$message.error(`文件只能是 ${this.fileAccept} 格式!`)
|
||||
this.$message.error("文件只能是 " + this.fileAccept + " 格式!")
|
||||
return false
|
||||
},
|
||||
|
||||
@@ -441,15 +444,15 @@ module.exports = {
|
||||
let result = []
|
||||
if (response && response.code === 0 && file.status === "success") {
|
||||
fileList.forEach((f) => {
|
||||
if(f.response.data?.includes('webvpn')) {
|
||||
if(f.response && f.response.data && f.response.data.includes('webvpn')) {
|
||||
const index = f.response.data.indexOf('/platform')
|
||||
f.response.data = f.response.data.substring(index)
|
||||
this.$set(f.response, "data", f.response.data.substring(index))
|
||||
}
|
||||
result.push(f)
|
||||
})
|
||||
} else {
|
||||
this.$message.error(response.msg)
|
||||
fileList = fileList.splice(file, 1)
|
||||
this.$message.error(response && response.msg ? response.msg : "文件上传失败,请稍后重试")
|
||||
fileList = this.filterFileList(fileList, file)
|
||||
}
|
||||
if (result.length > 0) {
|
||||
if (this.upload_result_category === "interval") {
|
||||
@@ -484,6 +487,40 @@ module.exports = {
|
||||
this.$emit("update:value", fileList)
|
||||
},
|
||||
|
||||
handleError(error, file, fileList) {
|
||||
this.$message.error(this.getUploadErrorMessage(error))
|
||||
this.$emit("update:value", this.filterFileList(fileList, file))
|
||||
},
|
||||
|
||||
filterFileList(fileList, file) {
|
||||
if (!Array.isArray(fileList)) {
|
||||
return []
|
||||
}
|
||||
return fileList.filter((item) => {
|
||||
return !file || item.uid !== file.uid
|
||||
})
|
||||
},
|
||||
|
||||
getUploadErrorMessage(error) {
|
||||
if (error && error.response && error.response.data && error.response.data.msg) {
|
||||
return error.response.data.msg
|
||||
}
|
||||
if (error && error.status) {
|
||||
return "文件上传失败,服务器返回" + error.status
|
||||
}
|
||||
return "文件上传失败,请检查文件后重试"
|
||||
},
|
||||
|
||||
buildQrCodeAddress(timestamp) {
|
||||
const currentLength = Array.isArray(this.fileList) ? this.fileList.length : 0
|
||||
const remainNumber = this.upload_number ? Math.max(this.upload_number - currentLength, 0) : 0
|
||||
let address = origin + "/platform/sys/h5ScanCodeUploadFile?timestamp=" + encodeURIComponent(timestamp)
|
||||
address = address + "&upload_number=" + encodeURIComponent(remainNumber)
|
||||
address = address + "&upload_size=" + encodeURIComponent(this.upload_size || "")
|
||||
address = address + "&accept=" + encodeURIComponent(this.fileAccept || "")
|
||||
return address
|
||||
},
|
||||
|
||||
// 通过DOM获取上传的文件
|
||||
uploadFileList() {
|
||||
if (this.fileList) {
|
||||
@@ -546,7 +583,7 @@ module.exports = {
|
||||
|
||||
mounted() {
|
||||
const timestamp = new Date().getTime()
|
||||
this.qrCodeAddress = origin + "/platform/sys/h5ScanCodeUploadFile?timestamp=" + timestamp
|
||||
this.$set(this, "qrCodeAddress", this.buildQrCodeAddress(timestamp))
|
||||
console.info("qrCodeAddress:", this.qrCodeAddress)
|
||||
// 可选:轮询或 watch ready 状态
|
||||
const checkReady = () => {
|
||||
@@ -588,7 +625,7 @@ module.exports = {
|
||||
|
||||
if (validFiles.length < data.files.length) {
|
||||
data.files = validFiles
|
||||
this.$message.error(`文件只能是 ${this.fileAccept} 格式,已自动过滤掉不符合要求文件!`)
|
||||
this.$message.error("文件只能是 " + this.fileAccept + " 格式,已自动过滤掉不符合要求文件!")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,13 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.upload-tip {
|
||||
margin-top: 12px;
|
||||
color: #666;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.van-uploader__tip {
|
||||
color: var(--color-primary);
|
||||
font-size: 14px;
|
||||
@@ -78,8 +85,9 @@
|
||||
></van-uploader>
|
||||
|
||||
<div class="upload-button">
|
||||
<van-button block type="primary" @click="onSubmit">同步到电脑</van-button>
|
||||
<van-button block type="primary" :disabled="submitting" @click="onSubmit">同步到电脑</van-button>
|
||||
</div>
|
||||
<div class="upload-tip">{{ uploadTips }}</div>
|
||||
</div>
|
||||
</body>
|
||||
<script nonce="${cspNonce!}">
|
||||
@@ -93,22 +101,97 @@
|
||||
// accept: "image/*",
|
||||
accept: "*",
|
||||
upload_number: 9,
|
||||
upload_size: 1024 * 1024 * 10,
|
||||
submitting: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
uploadTips() {
|
||||
const sizeText = (this.upload_size / 1024 / 1024).toFixed(2)
|
||||
const acceptText = this.accept && this.accept !== "*" ? this.accept : "不限格式"
|
||||
return "最多选择" + this.upload_number + "个文件,支持" + acceptText + ",单个文件不超过" + sizeText + "M"
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.initUploadConfig()
|
||||
},
|
||||
methods: {
|
||||
GetQueryString(name) {
|
||||
return new URLSearchParams(window.location.search).get(name)
|
||||
},
|
||||
initUploadConfig() {
|
||||
const accept = this.GetQueryString("accept")
|
||||
const uploadNumber = parseInt(this.GetQueryString("upload_number"), 10)
|
||||
const uploadSize = parseInt(this.GetQueryString("upload_size"), 10)
|
||||
if (accept) {
|
||||
this.$set(this, "accept", accept)
|
||||
}
|
||||
if (!isNaN(uploadNumber)) {
|
||||
this.$set(this, "upload_number", uploadNumber)
|
||||
}
|
||||
if (!isNaN(uploadSize) && uploadSize > 0) {
|
||||
this.$set(this, "upload_size", uploadSize)
|
||||
}
|
||||
},
|
||||
beforeRead(file) {
|
||||
const files = Array.isArray(file) ? file : [file]
|
||||
if (this.fileList.length + files.length > this.upload_number) {
|
||||
this.$toast.fail("最多只能上传" + this.upload_number + "个文件")
|
||||
return false
|
||||
}
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const validMsg = this.validateFile(files[i])
|
||||
if (validMsg) {
|
||||
this.$toast.fail(validMsg)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
},
|
||||
afterRead(file) {},
|
||||
|
||||
buildUploadPromises(file) {
|
||||
return
|
||||
validateFile(file) {
|
||||
if (!file) {
|
||||
return "请选择要上传的文件"
|
||||
}
|
||||
if (file.size > this.upload_size) {
|
||||
return "文件过大,请上传小于" + (this.upload_size / 1024 / 1024).toFixed(2) + "M的文件"
|
||||
}
|
||||
if (!this.accept || this.accept === "*") {
|
||||
return ""
|
||||
}
|
||||
const fileName = file.name || ""
|
||||
const dotIndex = fileName.lastIndexOf(".")
|
||||
if (dotIndex === -1) {
|
||||
return "文件必须包含有效的扩展名"
|
||||
}
|
||||
const fileType = fileName.substring(dotIndex).toUpperCase()
|
||||
const acceptTypes = this.accept
|
||||
.trim()
|
||||
.split(",")
|
||||
.map((type) => type.trim().toUpperCase())
|
||||
if (acceptTypes.indexOf(fileType) === -1) {
|
||||
return "文件只能是 " + this.accept + " 格式"
|
||||
}
|
||||
return ""
|
||||
},
|
||||
|
||||
onSubmit() {
|
||||
if (this.fileList.length === 0) {
|
||||
this.$toast("请先选择文件")
|
||||
return
|
||||
}
|
||||
|
||||
const pendingFiles = this.fileList.filter((f) => {
|
||||
return f.file
|
||||
})
|
||||
|
||||
if (pendingFiles.length === 0) {
|
||||
this.syncPc()
|
||||
return
|
||||
}
|
||||
|
||||
this.$set(this, "submitting", true)
|
||||
const loading = this.$toast({
|
||||
duration: 0,
|
||||
overlay: true,
|
||||
@@ -118,37 +201,39 @@
|
||||
})
|
||||
|
||||
Promise.all(
|
||||
this.fileList.map((f) => {
|
||||
pendingFiles.map((f) => {
|
||||
const formData = new FormData()
|
||||
formData.append("file", f.file)
|
||||
return axios.post("/platform/sys/h5ScanCodeUploadFile/upload", formData).then((axiosResp) => {
|
||||
const resp = axiosResp.data
|
||||
if (resp.code === 0) {
|
||||
console.log(resp)
|
||||
f.name = f.file.name
|
||||
f.size = f.file.size
|
||||
f.status = null
|
||||
f.url = resp.data
|
||||
f.response = resp
|
||||
f.percentage = 100
|
||||
f.isImage = true
|
||||
this.$set(f, "name", f.file.name)
|
||||
this.$set(f, "size", f.file.size)
|
||||
this.$set(f, "status", null)
|
||||
this.$set(f, "url", resp.data)
|
||||
this.$set(f, "response", resp)
|
||||
this.$set(f, "percentage", 100)
|
||||
this.$set(f, "isImage", this.isImageFile(f.file.name))
|
||||
delete f.file
|
||||
delete f.content
|
||||
console.log(f)
|
||||
} else {
|
||||
f.status = "fail"
|
||||
this.$set(f, "status", "fail")
|
||||
return Promise.reject(new Error(resp.msg || "文件上传失败"))
|
||||
}
|
||||
}).catch((error) => {
|
||||
this.$set(f, "status", "fail")
|
||||
return Promise.reject(error)
|
||||
})
|
||||
})
|
||||
)
|
||||
.then(() => {
|
||||
loading.close()
|
||||
console.log(this.fileList)
|
||||
this.syncPc()
|
||||
})
|
||||
.catch((error) => {
|
||||
loading.close()
|
||||
this.$toast.fail(error)
|
||||
this.$set(this, "submitting", false)
|
||||
this.$toast.fail(this.getErrorMessage(error, "文件上传失败"))
|
||||
})
|
||||
},
|
||||
|
||||
@@ -157,15 +242,16 @@
|
||||
this.$toast('请先选择文件')
|
||||
return
|
||||
}
|
||||
console.log(this.fileList)
|
||||
|
||||
this.$set(this, "submitting", true)
|
||||
const params = new URLSearchParams()
|
||||
params.append("files", JSON.stringify(this.fileList))
|
||||
params.append("timestamp", this.GetQueryString("timestamp") || "")
|
||||
|
||||
axios
|
||||
.post(
|
||||
"/platform/sys/h5ScanCodeUploadFile/syncPc",
|
||||
{
|
||||
files: JSON.stringify(this.fileList),
|
||||
timestamp: this.GetQueryString("timestamp")
|
||||
},
|
||||
params.toString(),
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8"
|
||||
@@ -176,9 +262,33 @@
|
||||
if (resp.data.code === 0) {
|
||||
this.$toast.success("上传成功,请关注电脑端信息")
|
||||
} else {
|
||||
this.$toast.fail(resp.data.msg)
|
||||
this.$toast.fail(resp.data.msg || "同步到电脑失败")
|
||||
}
|
||||
this.$set(this, "submitting", false)
|
||||
})
|
||||
.catch((error) => {
|
||||
this.$toast.fail(this.getErrorMessage(error, "同步到电脑失败"))
|
||||
this.$set(this, "submitting", false)
|
||||
})
|
||||
},
|
||||
|
||||
isImageFile(fileName) {
|
||||
const name = fileName || ""
|
||||
const suffix = name.substring(name.lastIndexOf(".") + 1).toLowerCase()
|
||||
return ["jpg", "jpeg", "png", "gif", "svg"].indexOf(suffix) !== -1
|
||||
},
|
||||
|
||||
getErrorMessage(error, defaultMsg) {
|
||||
if (error && error.response && error.response.data && error.response.data.msg) {
|
||||
return error.response.data.msg
|
||||
}
|
||||
if (error && error.message && error.message !== "Network Error") {
|
||||
return defaultMsg + ":" + error.message
|
||||
}
|
||||
if (error && error.response && error.response.status) {
|
||||
return defaultMsg + ",服务器返回" + error.response.status
|
||||
}
|
||||
return defaultMsg + ",请稍后重试"
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user