因为这个版本的若依plus不支持本地文件上传,所以需要增加这些本地上传文件的后端代码
和前端代码修改。
1、后端部分
先配置跳过测试吧,平时编译也不需要这个
<!--添加配置跳过测试--> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.2</version> <configuration> <skipTests>true</skipTests> </configuration> </plugin> <!--添加配置跳过测试-->
增加一个公共上传接口
package com.ruoyi.web.controller.common; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.HashMap; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import com.ruoyi.common.config.RuoYiConfig; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.core.domain.R; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.file.FileUploadUtils; import com.ruoyi.common.utils.file.FileUtils; import com.ruoyi.framework.config.ServerConfig; /** * 通用请求处理 * * @author ruoyi */ @RestController @RequestMapping("/common") public class CommonController { private static final Logger log = LoggerFactory.getLogger(CommonController.class); @Autowired private ServerConfig serverConfig; private static final String FILE_DELIMETER = ","; /** * 通用下载请求 * * @param fileName 文件名称 * @param delete 是否删除 */ @GetMapping("/download") public void fileDownload(String fileName, Boolean delete, HttpServletResponse response, HttpServletRequest request) { try { if (!FileUtils.checkAllowDownload(fileName)) { throw new Exception(StringUtils.format("文件名称({})非法,不允许下载。 ", fileName)); } String realFileName = System.currentTimeMillis() + fileName.substring(fileName.indexOf("_") + 1); String filePath = RuoYiConfig.getDownloadPath() + fileName; response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); FileUtils.setAttachmentResponseHeader(response, realFileName); FileUtils.writeBytes(filePath, response.getOutputStream()); if (delete) { FileUtils.deleteFile(filePath); } } catch (Exception e) { log.error("下载文件失败", e); } } /** * 通用上传请求(单个) */ @PostMapping("/upload") @ResponseBody public R<Map<String, String>> uploadFile(MultipartFile file) throws Exception { try { // 上传文件路径 String filePath = RuoYiConfig.getUploadPath(); // 上传并返回新文件名称 String fileName = FileUploadUtils.upload(filePath, file); String url = serverConfig.getUrl() + fileName; Map<String, String> map = new HashMap<>(2); map.put("url", url); map.put("fileName", fileName); map.put("newFileName", FileUtils.getName(fileName)); map.put("originalFilename", file.getOriginalFilename()); return R.ok(map); } catch (Exception e) { return R.fail(e.getMessage()); } } /** * 通用上传请求(多个) */ @PostMapping("/uploads") @ResponseBody public R<Map<String, String>> uploadFiles(List<MultipartFile> files) throws Exception { try { // 上传文件路径 String filePath = RuoYiConfig.getUploadPath(); List<String> urls = new ArrayList<String>(); List<String> fileNames = new ArrayList<String>(); List<String> newFileNames = new ArrayList<String>(); List<String> originalFilenames = new ArrayList<String>(); for (MultipartFile file : files) { // 上传并返回新文件名称 String fileName = FileUploadUtils.upload(filePath, file); String url = serverConfig.getUrl() + fileName; urls.add(url); fileNames.add(fileName); newFileNames.add(FileUtils.getName(fileName)); originalFilenames.add(file.getOriginalFilename()); } Map<String, String> map = new HashMap<>(2); map.put("urls", StringUtils.join(urls, FILE_DELIMETER)); map.put("fileNames", StringUtils.join(fileNames, FILE_DELIMETER)); map.put("newFileNames", StringUtils.join(newFileNames, FILE_DELIMETER)); map.put("originalFilenames", StringUtils.join(originalFilenames, FILE_DELIMETER)); return R.ok(map); } catch (Exception e) { return R.fail(e.getMessage()); } } /** * 本地资源通用下载 */ @GetMapping("/download/resource") public void resourceDownload(String resource, HttpServletRequest request, HttpServletResponse response) throws Exception { try { if (!FileUtils.checkAllowDownload(resource)) { throw new Exception(StringUtils.format("资源文件({})非法,不允许下载。 ", resource)); } // 本地资源路径 String localPath = RuoYiConfig.getProfile(); // 数据库资源地址 String downloadPath = localPath + StringUtils.substringAfter(resource, Constants.RESOURCE_PREFIX); // 下载名称 String downloadName = StringUtils.substringAfterLast(downloadPath, "/"); response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); FileUtils.setAttachmentResponseHeader(response, downloadName); FileUtils.writeBytes(downloadPath, response.getOutputStream()); } catch (Exception e) { log.error("下载文件失败", e); } } }
增加上传的全局参数
# 本地:local\Minio:minio\阿里云:alioss uploadtype: local #文件上传根目录 设置 profile: /home/nbcio
增加文件工具类
package com.ruoyi.common.utils.file; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.ArrayUtils; import com.ruoyi.common.config.RuoYiConfig; import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.uuid.IdUtils; import org.apache.commons.io.FilenameUtils; /** * 文件处理工具类 * * @author ruoyi */ public class FileUtils { public static String FILENAME_PATTERN = "[a-zA-Z0-9_\\-\\|\\.\\u4e00-\\u9fa5]+"; /** * 输出指定文件的byte数组 * * @param filePath 文件路径 * @param os 输出流 * @return */ public static void writeBytes(String filePath, OutputStream os) throws IOException { FileInputStream fis = null; try { File file = new File(filePath); if (!file.exists()) { throw new FileNotFoundException(filePath); } fis = new FileInputStream(file); byte[] b = new byte[1024]; int length; while ((length = fis.read(b)) > 0) { os.write(b, 0, length); } } catch (IOException e) { throw e; } finally { IOUtils.close(os); IOUtils.close(fis); } } /** * 写数据到文件中 * * @param data 数据 * @return 目标文件 * @throws IOException IO异常 */ public static String writeImportBytes(byte[] data) throws IOException { return writeBytes(data, RuoYiConfig.getImportPath()); } /** * 写数据到文件中 * * @param data 数据 * @param uploadDir 目标文件 * @return 目标文件 * @throws IOException IO异常 */ public static String writeBytes(byte[] data, String uploadDir) throws IOException { FileOutputStream fos = null; String pathName = ""; try { String extension = getFileExtendName(data); pathName = DateUtils.datePath() + "/" + IdUtils.fastUUID() + "." + extension; File file = FileUploadUtils.getAbsoluteFile(uploadDir, pathName); fos = new FileOutputStream(file); fos.write(data); } finally { IOUtils.close(fos); } return FileUploadUtils.getPathFileName(uploadDir, pathName); } /** * 删除文件 * * @param filePath 文件 * @return */ public static boolean deleteFile(String filePath) { boolean flag = false; File file = new File(filePath); // 路径为文件且不为空则进行删除 if (file.isFile() && file.exists()) { flag = file.delete(); } return flag; } /** * 文件名称验证 * * @param filename 文件名称 * @return true 正常 false 非法 */ public static boolean isValidFilename(String filename) { return filename.matches(FILENAME_PATTERN); } /** * 检查文件是否可下载 * * @param resource 需要下载的文件 * @return true 正常 false 非法 */ public static boolean checkAllowDownload(String resource) { // 禁止目录上跳级别 if (StringUtils.contains(resource, "..")) { return false; } // 检查允许下载的文件规则 if (ArrayUtils.contains(MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION, FileTypeUtils.getFileType(resource))) { return true; } // 不在允许下载的文件规则 return false; } /** * 下载文件名重新编码 * * @param request 请求对象 * @param fileName 文件名 * @return 编码后的文件名 */ public static String setFileDownloadHeader(HttpServletRequest request, String fileName) throws UnsupportedEncodingException { final String agent = request.getHeader("USER-AGENT"); String filename = fileName; if (agent.contains("MSIE")) { // IE浏览器 filename = URLEncoder.encode(filename, "utf-8"); filename = filename.replace("+", " "); } else if (agent.contains("Firefox")) { // 火狐浏览器 filename = new String(fileName.getBytes(), "ISO8859-1"); } else if (agent.contains("Chrome")) { // google浏览器 filename = URLEncoder.encode(filename, "utf-8"); } else { // 其它浏览器 filename = URLEncoder.encode(filename, "utf-8"); } return filename; } /** * 下载文件名重新编码 * * @param response 响应对象 * @param realFileName 真实文件名 */ public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) throws UnsupportedEncodingException { String percentEncodedFileName = percentEncode(realFileName); StringBuilder contentDispositionValue = new StringBuilder(); contentDispositionValue.append("attachment; filename=") .append(percentEncodedFileName) .append(";") .append("filename*=") .append("utf-8''") .append(percentEncodedFileName); response.addHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename"); response.setHeader("Content-disposition", contentDispositionValue.toString()); response.setHeader("download-filename", percentEncodedFileName); } /** * 百分号编码工具方法 * * @param s 需要百分号编码的字符串 * @return 百分号编码后的字符串 */ public static String percentEncode(String s) throws UnsupportedEncodingException { String encode = URLEncoder.encode(s, StandardCharsets.UTF_8.toString()); return encode.replaceAll("\\+", "%20"); } /** * 获取图像后缀 * * @param photoByte 图像数据 * @return 后缀名 */ public static String getFileExtendName(byte[] photoByte) { String strFileExtendName = "jpg"; if ((photoByte[0] == 71) && (photoByte[1] == 73) && (photoByte[2] == 70) && (photoByte[3] == 56) && ((photoByte[4] == 55) || (photoByte[4] == 57)) && (photoByte[5] == 97)) { strFileExtendName = "gif"; } else if ((photoByte[6] == 74) && (photoByte[7] == 70) && (photoByte[8] == 73) && (photoByte[9] == 70)) { strFileExtendName = "jpg"; } else if ((photoByte[0] == 66) && (photoByte[1] == 77)) { strFileExtendName = "bmp"; } else if ((photoByte[1] == 80) && (photoByte[2] == 78) && (photoByte[3] == 71)) { strFileExtendName = "png"; } return strFileExtendName; } /** * 获取文件名称 /profile/upload/2022/04/16/ruoyi.png -- ruoyi.png * * @param fileName 路径名称 * @return 没有文件路径的名称 */ public static String getName(String fileName) { if (fileName == null) { return null; } int lastUnixPos = fileName.lastIndexOf('/'); int lastWindowsPos = fileName.lastIndexOf('\\'); int index = Math.max(lastUnixPos, lastWindowsPos); return fileName.substring(index + 1); } /** * 获取不带后缀文件名称 /profile/upload/2022/04/16/ruoyi.png -- ruoyi * * @param fileName 路径名称 * @return 没有文件路径和后缀的名称 */ public static String getNameNotSuffix(String fileName) { if (fileName == null) { return null; } String baseName = FilenameUtils.getBaseName(fileName); return baseName; } }
2、前端方面
previewRender.js修改如下:
import { isAttr,jsonClone } from '../utils'; import childrenItem from './slot/index'; import {remoteData} from './mixin'; import { getToken } from "@/utils/auth"; //先修改在这里,后续需要优化 function vModel(self, dataObject) { dataObject.props.value = self.value; dataObject.on.input = val => { self.$emit('input', val) } //判断是否为上传组件 if(self.conf.compType === 'upload'){ //for token add by nbacheng 2022-09-07 //dataObject.attrs['headers'] = {"Authorization":"Bearer " + getToken()}; /** * 此处增加自定义的token,如果不能满足要求,可以重写此处代码 */ const token = getToken(); dataObject.attrs['headers'] = {"Authorization":"Bearer " + token}; console.log("dataObject.props.value",dataObject.props.value) if(dataObject.props.value!==undefined && dataObject.props.value !==''){ const filevalue = JSON.parse(dataObject.props.value); dataObject.props['file-list'] = filevalue; } dataObject.attrs['before-upload'] = file=>{ //非限定后缀不允许上传 console.log("before-upload file",file); const fileName = file.name; console.log("before-upload fileName",fileName); const suffixName = fileName.split('.').pop(); if(!self.conf.accept.includes(suffixName)){ self.$message.error('该后缀文件不允许上传'); return false; } const fileSize = file.size; if(fileSize>dataObject.props.fileSize*1024*1024){ self.$message.error('文件大小超出限制,请检查!'); return false; } } //for get return file url add by nbacheng 2022-09-07 dataObject.attrs['on-success'] = file=>{ console.log("on-success file",file); var filename=file.data.fileName.substring(file.data.fileName.lastIndexOf('/')+1) //获取文件名称 let fileObj = {name: filename, url: file.data.fileName} console.log("dataObject=",dataObject); console.log("self.conf=",self.conf); let oldValue = []; if(dataObject.props.value) { oldValue = JSON.parse(dataObject.props.value); }else { oldValue = []; } if (oldValue) { oldValue.push(fileObj) } else { oldValue = [fileObj] } self.$emit('input',JSON.stringify(oldValue)); console.log("on-success value",oldValue); } dataObject.attrs['on-remove'] = (file, fileList) => { console.log("on-remove file,fileList",file,fileList); let oldValue = JSON.parse(dataObject.props.value); console.log("on-remove oldValue",oldValue); //file 删除的文件 //过滤掉删除的文件 let newValue = oldValue.filter(item => item.name !== file.name) self.$emit('input',JSON.stringify(newValue)); console.log("on-remove newValue",newValue); } dataObject.attrs['on-error'] = (file) => { console.log("on-error file",file); } dataObject.attrs['on-preview'] = (file) => { console.log("on-preview file",file); //download(file); } //for get return file url add by nbacheng 2022-09-07 } } export default { render(h) { let dataObject = { attrs: {}, props: {}, on: {}, style: {} } //远程获取数据 this.getRemoteData(); const confClone = jsonClone(this.conf); const children = childrenItem(h,confClone); Object.keys(confClone).forEach(key => { const val = confClone[key] if (dataObject[key]) { dataObject[key] = val } else if(key ==='width'){ dataObject.style= 'width:'+val; } else if (!isAttr(key)) { dataObject.props[key] = val }else { if (key == 'classStyle' && val.length > 0){ let style ="" val.forEach(item =>{ console.log(item) style+=item +" " }) dataObject.attrs['class'] = style }else if (key == 'cssStyle'){ dataObject.attrs['style'] = val }else if(key !== 'value'){ dataObject.attrs[key] = val } } }) /*调整赋值模式,规避cascader组件赋值props会出现覆盖预制参数的bug */ vModel(this, dataObject); return h(confClone.ele, dataObject, children) }, props: ['conf','value'], mixins:[remoteData] }
3、效果图