最近遇到一个问题,上传图片到服务器以后,回显不了,报错404;
历时三天终于找到解决办法:
1.后端代码:
@RestController
@RequestMapping("file")
@SuppressWarnings({"unchecked","rawtypes"})
public class FileController{@Autowiredprivate ConfigService configService;/*** 上传文件*/@RequestMapping("/upload")public R upload(@RequestParam("file") MultipartFile file,String type) throws Exception {if (file.isEmpty()) {throw new EIException("上传文件不能为空");}String fileExt = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".")+1);
// File path = new File(ResourceUtils.getURL("classpath:static").getPath());
// if(!path.exists()) {
// path = new File("");
// }
// File upload = new File(ResourceUtils.getURL("classpath:static").getPath()+"\\upload");
// if(!upload.exists()) {
// upload.mkdirs();
// }String fileName = new Date().getTime()+"."+fileExt;
//这里写自己上传的路径File dest = new File("D:\\Edge\\537\\daima\\springboot60zv5\\src\\main\\resources\\static\\upload"+"\\"+fileName);file.transferTo(dest);if(StringUtils.isNotBlank(type) && type.equals("1")) {ConfigEntity configEntity = configService.selectOne(new EntityWrapper<ConfigEntity>().eq("name", "faceFile"));if(configEntity==null) {configEntity = new ConfigEntity();configEntity.setName("faceFile");configEntity.setValue(fileName);} else {configEntity.setValue(fileName);}configService.insertOrUpdate(configEntity);}return R.ok().put("file", fileName);}@IgnoreAuth@RequestMapping("/download")public ResponseEntity<byte[]> download(@RequestParam String fileName) {try {File path = new File(ResourceUtils.getURL("classpath:static").getPath());if(!path.exists()) {path = new File("");}File upload = new File(path.getAbsolutePath(),"/upload/");if(!upload.exists()) {upload.mkdirs();}File file = new File(upload.getAbsolutePath()+"/"+fileName);if(file.exists()){/*if(!fileService.canRead(file, SessionManager.getSessionUser())){getResponse().sendError(403);}*/HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);headers.setContentDispositionFormData("attachment", fileName);return new ResponseEntity<byte[]>(FileUtils.readFileToByteArray(file),headers, HttpStatus.CREATED);}} catch (IOException e) {e.printStackTrace();}return new ResponseEntity<byte[]>(HttpStatus.INTERNAL_SERVER_ERROR);}}
2.前端代码:
<el-form-item class="upload" v-if="type!='info' && !ro.touxiang" label="头像" prop="touxiang"><file-uploadtip="点击上传头像"action="file/upload":limit="3":multiple="true":fileUrls="ruleForm.touxiang?ruleForm.touxiang:''"@change="touxiangUploadChange"></file-upload></el-form-item>
<template><div><!-- 上传文件组件 --><el-uploadref="upload":action="getActionUrl"list-type="picture-card":multiple="multiple":limit="limit":headers="myHeaders":file-list="fileList":on-exceed="handleExceed":on-preview="handleUploadPreview":on-remove="handleRemove":on-success="handleUploadSuccess":on-error="handleUploadErr":before-upload="handleBeforeUpload"><i class="el-icon-plus"></i><div slot="tip" class="el-upload__tip" style="color:#838fa1;">{{tip}}</div></el-upload><el-dialog :visible.sync="dialogVisible" size="tiny" append-to-body><img width="100%" :src="dialogImageUrl" alt></el-dialog></div>
</template>
<script>
import storage from "@/utils/storage";
import base from "@/utils/base";
export default {data() {return {// 查看大图dialogVisible: false,// 查看大图dialogImageUrl: "",// 组件渲染图片的数组字段,有特殊格式要求fileList: [],fileUrlList: [],myHeaders:{}};},props: ["tip", "action", "limit", "multiple", "fileUrls"],mounted() {this.init();this.myHeaders= {'Token':storage.get("Token")}},watch: {fileUrls: function(val, oldVal) {// console.log("new: %s, old: %s", val, oldVal);this.init();}},computed: {// 计算属性的 gettergetActionUrl: function() {// return base.url + this.action + "?token=" + storage.get("token");return `/${this.$base.name}/` + this.action;}},methods: {// 初始化init() {// console.log(this.fileUrls);if (this.fileUrls) {this.fileUrlList = this.fileUrls.split(",");let fileArray = [];this.fileUrlList.forEach(function(item, index) {var url = item;var name = index;var file = {name: name,url: url};fileArray.push(file);});this.setFileList(fileArray);}},handleBeforeUpload(file) {},// 上传文件成功后执行handleUploadSuccess(res, file, fileList) {if (res && res.code === 0) {fileList[fileList.length - 1]["url"] =this.$base.url + "upload/" + file.response.file;this.setFileList(fileList);this.$emit("change", this.fileUrlList.join(","));} else {this.$message.error(res.msg);}},// 图片上传失败handleUploadErr(err, file, fileList) {this.$message.error("文件上传失败");},// 移除图片handleRemove(file, fileList) {this.setFileList(fileList);this.$emit("change", this.fileUrlList.join(","));},// 查看大图handleUploadPreview(file) {this.dialogImageUrl = file.url;this.dialogVisible = true;},// 限制图片数量handleExceed(files, fileList) {this.$message.warning(`最多上传${this.limit}张图片`);},// 重新对fileList进行赋值setFileList(fileList) {var fileArray = [];var fileUrlArray = [];// 有些图片不是公开的,所以需要携带token信息做权限校验var token = storage.get("token");fileList.forEach(function(item, index) {var url = item.url.split("?")[0];var name = item.name;var file = {name: name,url: url + "?token=" + token};fileArray.push(file);fileUrlArray.push(url);});this.fileList = fileArray;this.fileUrlList = fileUrlArray;}}
};
</script>
<style lang="scss" scoped>
</style>
<template><div><!-- 图片上传组件辅助--><el-uploadclass="avatar-uploader":action="getActionUrl"name="file":headers="header":show-file-list="false":on-success="uploadSuccess":on-error="uploadError":before-upload="beforeUpload"></el-upload><quill-editorclass="editor"v-model="value"ref="myQuillEditor":options="editorOption"@blur="onEditorBlur($event)"@focus="onEditorFocus($event)"@change="onEditorChange($event)"></quill-editor></div>
</template>
<script>
// 工具栏配置
const toolbarOptions = [["bold", "italic", "underline", "strike"], // 加粗 斜体 下划线 删除线["blockquote", "code-block"], // 引用 代码块[{ header: 1 }, { header: 2 }], // 1、2 级标题[{ list: "ordered" }, { list: "bullet" }], // 有序、无序列表[{ script: "sub" }, { script: "super" }], // 上标/下标[{ indent: "-1" }, { indent: "+1" }], // 缩进// [{'direction': 'rtl'}], // 文本方向[{ size: ["small", false, "large", "huge"] }], // 字体大小[{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题[{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色[{ font: [] }], // 字体种类[{ align: [] }], // 对齐方式["clean"], // 清除文本格式["link", "image", "video"] // 链接、图片、视频
];import { quillEditor } from "vue-quill-editor";
import "quill/dist/quill.core.css";
import "quill/dist/quill.snow.css";
import "quill/dist/quill.bubble.css";export default {props: {/*编辑器的内容*/value: {type: String},action: {type: String},/*图片大小*/maxSize: {type: Number,default: 4000 //kb}},components: {quillEditor},data() {return {content: this.value,quillUpdateImg: false, // 根据图片上传状态来确定是否显示loading动画,刚开始是false,不显示editorOption: {placeholder: "",theme: "snow", // or 'bubble'modules: {toolbar: {container: toolbarOptions,// container: "#toolbar",handlers: {image: function(value) {if (value) {// 触发input框选择图片文件document.querySelector(".avatar-uploader input").click();} else {this.quill.format("image", false);}}// link: function(value) {// if (value) {// var href = prompt('请输入url');// this.quill.format("link", href);// } else {// this.quill.format("link", false);// }// },}}}},// serverUrl: `${base.url}sys/storage/uploadSwiper?token=${storage.get('token')}`, // 这里写你要上传的图片服务器地址header: {// token: sessionStorage.token'Token': this.$storage.get("Token")} // 有的图片服务器要求请求头需要有token};},computed: {// 计算属性的 gettergetActionUrl: function() {// return this.$base.url + this.action + "?token=" + this.$storage.get("token");return `/${this.$base.name}/` + this.action;}},methods: {onEditorBlur() {//失去焦点事件},onEditorFocus() {//获得焦点事件},onEditorChange() {console.log(this.value);//内容改变事件this.$emit("input", this.value);},// 富文本图片上传前beforeUpload() {// 显示loading动画this.quillUpdateImg = true;},uploadSuccess(res, file) {// res为图片服务器返回的数据// 获取富文本组件实例let quill = this.$refs.myQuillEditor.quill;// 如果上传成功if (res.code === 0) {// 获取光标所在位置let length = quill.getSelection().index;// 插入图片 res.url为服务器返回的图片地址quill.insertEmbed(length, "image", this.$base.url+ "upload/" +res.file);// 调整光标到最后quill.setSelection(length + 1);} else {this.$message.error("图片插入失败");}// loading动画消失this.quillUpdateImg = false;},// 富文本图片上传失败uploadError() {// loading动画消失this.quillUpdateImg = false;this.$message.error("图片插入失败");}}
};
</script> <style>
.editor {line-height: normal !important;height: 400px;
}
.ql-snow .ql-tooltip[data-mode="link"]::before {content: "请输入链接地址:";
}
.ql-snow .ql-tooltip.ql-editing a.ql-action::after {border-right: 0px;content: "保存";padding-right: 0px;
}.ql-snow .ql-tooltip[data-mode="video"]::before {content: "请输入视频地址:";
}.ql-snow .ql-picker.ql-size .ql-picker-label::before,
.ql-snow .ql-picker.ql-size .ql-picker-item::before {content: "14px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="small"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="small"]::before {content: "10px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="large"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="large"]::before {content: "18px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="huge"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="huge"]::before {content: "32px";
}.ql-snow .ql-picker.ql-header .ql-picker-label::before,
.ql-snow .ql-picker.ql-header .ql-picker-item::before {content: "文本";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {content: "标题1";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {content: "标题2";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {content: "标题3";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {content: "标题4";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {content: "标题5";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {content: "标题6";
}.ql-snow .ql-picker.ql-font .ql-picker-label::before,
.ql-snow .ql-picker.ql-font .ql-picker-item::before {content: "标准字体";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="serif"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="serif"]::before {content: "衬线字体";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="monospace"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="monospace"]::before {content: "等宽字体";
}
</style>
3.解决404问题的关键:
package com.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;import com.interceptor.AuthorizationInterceptor;@Configuration
public class InterceptorConfig extends WebMvcConfigurationSupport{@Beanpublic AuthorizationInterceptor getAuthorizationInterceptor() {return new AuthorizationInterceptor();}@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(getAuthorizationInterceptor()).addPathPatterns("/**").excludePathPatterns("/static/**");super.addInterceptors(registry);}/*** springboot 2.0配置WebMvcConfigurationSupport之后,会导致默认配置被覆盖,要访问静态资源需要重写addResourceHandlers方法*/@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {/* registry.addResourceHandler("/**").addResourceLocations("classpath:/resources/").addResourceLocations("classpath:/static/").addResourceLocations("classpath:/admin/").addResourceLocations("classpath:/front/").addResourceLocations("classpath:/public/");super.addResourceHandlers(registry);*/registry.addResourceHandler("/**").addResourceLocations("classpath:/resources/").addResourceLocations("classpath:/static/").addResourceLocations("classpath:/upload/").addResourceLocations("classpath:/admin/").addResourceLocations("classpath:/front/").addResourceLocations("classpath:/public/");registry.addResourceHandler("/upload/**").addResourceLocations("file:D:\\Edge\\537\\daima\\springboot60zv5\\src\\main\\resources\\static\\upload\\");super.addResourceHandlers(registry);}
}
需要再config中写下这段代码;
-
registry.addResourceHandler("/upload/**")
:这行代码使用了registry
对象(可能是ResourceHandlerRegistry
或类似的注册表),向其中添加一个资源处理器。 -
"/upload/**"
:这是一个路径模式,表示要匹配的资源路径。/**
表示匹配任意子路径,所以/upload/
及其子路径下的资源都会被这个处理器处理。 -
.addResourceLocations("file:D:\\Edge\\537\\daima\\springboot60zv5\\src\\main\\resources\\static\\upload\\")
:这行代码指定了资源的位置。在这个例子中,资源位于本地文件系统的指定路径中。
综合起来,这段代码的作用是将所有对/upload/
及其子路径的请求都路由到本地文件系统的指定路径中。这样,当发送/upload/
或其下的请求时,服务器将会返回该路径下的文件。
registry.addResourceHandler("/upload/**").addResourceLocations("file:D:\\Edge\\537\\daima\\springboot60zv5\\src\\main\\resources\\static\\upload\\");