Vue SpringBoot实现Html和Markdown格式内容(含图片上传)保存到MySQL
实现功能1.本文代码实现了前端Markdown格式的博文保存到MySQL的功能。2. 包括文章中图片的上传,在用户选择图片后就将其传到后端并将图片的链接返回给前端,填入到指定的位置。遇到的问题由于Markdown编辑器原因,返回的图片路径不能有\与空格如果遇到第二次进入编辑页面不能显示文章内容,那么在下方getArticle()方法中,处理响应的最后一行加入// 解决第二次进入不能显示...
实现功能
- 本文代码实现了前端Html、Markdown格式的博文保存到MySQL的功能。
- 包括文章中图片的上传,在用户选择图片后就将其传到后端并将图片的链接返回给前端,填入到指定的位置。
遇到的问题
- 由于Markdown编辑器原因,返回的图片路径不能有\与空格
- 如果遇到第二次进入编辑页面不能显示文章内容,那么在下方getArticle()方法中,处理响应的最后一行加入
// 解决第二次进入不能显示内容bug this.$refs.md.d_value = response.data.markdownContent
- Html格式内容中的部分特殊符号会被JAVA替换掉,导致回显的页面样式有出入。建议不使用Html格式,使用Markdown格式
前端
前端是用Vue-cli搭建的项目工程,运行在WebStorm中。
安装依赖
-
数据请求相关
npm install axios --save npm install qs --save
-
安装Markdown编辑器
npm install mavon-editor --save
使用mavon-editor,请自行参考如何使用。目前不支持流程图、序列图、甘特图
-
其他依赖
npm install style-loader npm install css-loader npm install sass-loader npm install babel-loader --save
在main.js中引入mavonEditor
import mavonEditor from 'mavon-editor'
import 'mavon-editor/dist/css/index.css'
Vue.use(mavonEditor)
ArticleMarkdown.vue组件,实现Markdown博文的存取
代码导读
getArticle():通过文章id获取内容
saveArticle():提交博文内容到后端。同时提交了html、markdown格式的内容,见
var htmlCode = this.$refs.md.d_render;
var markdownCode = this.$refs.md.d_value;
imgAdd(pos, file):上传单张图片。file图片对象,pos图片下标,后端返回图片链接地址时,用于定位
imgDel(pos):删除图片
mulUploadimg() :上传多张图片。图片对象存放在data中img_file对象中
imgDelMul(pos):删除多张图片。
源代码
<template>
<div>
<mavon-editor ref="md" class="md" v-model="sqlData.markdown" @imgAdd="imgAdd" @imgDel="imgDel" @save="saveArticle"/>
</div>
</template>
<script>
import axios from 'axios'
import qs from 'qs'
const area_axios = axios.create({
headers: {'Content-Type': 'application/json;charset=utf-8',},// 设置传输内容的类型和编码
withCredentials: true,// 指定某个请求应该发送凭据
})
const file_axios = axios.create({
headers: {'Content-Type': 'multipart/form-data',},// 设置传输内容的类型和编码
withCredentials: true,// 指定某个请求应该发送凭据
})
const area_form_axios = axios.create({
headers: {'Content-Type': 'application/x-www-form-urlencoded',},// 设置传输内容的类型和编码
withCredentials: true,// 指定某个请求应该发送凭据
})
export default {
name: "Markdown",
data() {
return {
sqlData:{
markdown:'',
html:''
},
img_file: {},// 一次上次多张图片时使用
};
},
mounted:function (){
getArticle()
},
methods: {
// 获取文章
getArticle(){
area_form_axios.get('/api/get',{
params:{id: 12 }
},)
.then(response => {
console.log(this.sqlData)
this.sqlData = response.data
})
.catch(err => {
alert("请求失败")
})
},
// 保存文章
saveArticle(){
var htmlCode = this.$refs.md.d_render;
var markdownCode = this.$refs.md.d_value;
if(htmlCode.length == 0 || markdownCode.length == 0){
alert("请填写")
return;
}
area_axios({
url: '/api/add',
method: 'post',
data: JSON.stringify({'markdown':markdownCode,'html':htmlCode}),
}).then((response) => {
if(response.data > 0){
alert("成功")
}else {
alert("失败")
}
})
},
// 添加图片
imgAdd(pos, file){
console.log("pos:"+pos)
// 第一步.将图片上传到服务器.
var formdata = new FormData();
formdata.append('pic', file);
file_axios({
url: '/api/img_upload',
method: 'post',
data: formdata,
}).then((response) => {
// 第二步.将返回的url替换到文本原位置
var url = response.data;
//通过引入对象获取: import {mavonEditor} from ... 等方式引入后,此时$vm即为mavonEditor
//通过$refs获取: html声明ref : <mavon-editor ref=md ></mavon-editor>, 此时$vm为 this.$refs.md`
this.$refs.md.$img2Url(pos, url);
})
},
// 删除图片
imgDel(pos){
console.log("imgDel pos:"+pos)
},
// 多张图片
mulUploadimg(){
// 第一步.将图片上传到服务器.
var formdata = new FormData();
for(var _img in this.img_file){
debugger
// 后台需要图片的key一致
formdata.append('pics', this.img_file[_img]);
}
file_axios({
url: '/api/mul_img_upload',
method: 'post',
data: formdata,
}).then((res) => {
/**
* 例如:返回数据为 res = [[pos, url], [pos, url]...]
* pos 为原图片标志(0)
* url 为上传后图片的url地址
*/
// 第二步.将返回的url替换到文本原位置![...](0) -> ![...](url)
var idx_url = res.data;
idx_url.forEach(item => {
//通过引入对象获取: import {mavonEditor} from ... 等方式引入后,此时$vm即为mavonEditor
//通过$refs获取: html声明ref : <mavon-editor ref=md ></mavon-editor>, 此时$vm为 this.$refs.md`
this.$refs.md.$img2Url(item[0], item[1]);
});
})
},
// 多张图片
imgDelMul(pos){
console.log("imgDel pos:"+pos)
delete this.img_file[pos];
},
}
}
</script>
跨域配置
后端
文件上传相关配置
application.properties文件中
spring.servlet.multipart.enabled=true
# 最大支持文件大小
spring.servlet.multipart.max-file-size=10MB
# 最大支持请求大小
spring.servlet.multipart.max-request-size=50MB
配置拦截器
@Component
public class CrossDomainInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 允许客户端携带跨域cookie,此时origin值不能为“*”,只能为指定单一域名。!!开发时不要使用localhost访问
response.setHeader("Access-Control-Allow-Credentials", "true");
// 允许指定域访问跨域资源
//response.setHeader("Access-Control-Allow-Origin", "http://127.0.0.1:9006, http://127.0.0.1:8080");
response.setHeader("Access-Control-Allow-Origin", origin);// *
// 允许浏览器发送的请求消息头
//response.setHeader("Access-Control-Allow-Headers", "*");
response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers"));
// 允许浏览器在预检请求成功之后发送的实际请求方法名
//response.setHeader("Access-Control-Allow-Methods", "DEFAULT,POST,PATCH,PUT,OPTIONS,DELETE,HEAD");
response.setHeader("Access-Control-Allow-Methods", request.getHeader("Access-Control-Request-Method"));
// 浏览器缓存预检请求结果时间,单位:秒
response.setHeader("Access-Control-Max-Age", "86400");
return true;
}
}
数据接口
@Controller
@RequestMapping("")
public class MarkdownController {
/**
* 获取文章
* id: 文章id
* @author YSL
* 2019-03-04 15:38
*/
@GetMapping("/get")
@ResponseBody
public Bean test(@RequestParam("id")Integer id){
// 获取数据库中的数据,请自行实现。
return vueMarkdownMapper.query(id);
}
/**
* 保存文章到数据库。
* bean:前端传回JSON.stringify({'markdown':markdownCode,'html':htmlCode})格式的数据即可
* @author YSL
* 2019-03-04 15:26
*/
@PostMapping("/add")
@ResponseBody
public int test(@RequestBody Bean bean){
return vueMarkdownMapper.add(bean);// 保存数据到数据库,请自行实现
}
}
图片上传
代码导读
@RequestMapping("/img_upload"):单张图片上传,上传到blog_files/pictures目录下,返回图片url。
@RequestMapping("/mul_img_upload"):多张图片上传。上传到blog_files/pictures目录下,返回new String[]{图片下标, 图片url}格式的list。
fileUpload():文件上传。上传到blog_files/files目录下。
upload():真正实现文件上传的方法,基于MultipartFile实现
说明
- 图片与文件都是上传到tomcat/webapps/blog_files/目录下,blog_files是我专门用来保存图片的一个web工程,方便通过http访问到图片,给前端返回的图片地址也是http格式的。
- 在图片和文件上传的同时会备份,案例总备份路径:D:/webserver_bak/blog/
源代码
public class FileController {
/**
* 图片上传(一张)
* @param pic 需要上传的图片
* @return 图片url
* @author YSL
* 2019-03-01 17:14
*/
@RequestMapping("/img_upload")
@ResponseBody
public String imgUpload(@RequestParam(value = "pic", required = false) MultipartFile pic, HttpServletRequest request){
List<String> urlList = upload(new MultipartFile[]{pic}, "pictures", request);
return urlList != null ? urlList.get(0) : "";
}
/**
* 图片上传(多张)
* @param pics 需要上传的图片
* @return 图片下标和url
* @author YSL
* 2019-03-01 17:14
*/
@RequestMapping("/mul_img_upload")
@ResponseBody
public List<String[]> imgUpload(@RequestParam(value = "pics", required = false) MultipartFile[] pics, HttpServletRequest request){
List<String> urlList = upload(pics, "pictures", request);
List<String[]> list = new ArrayList<>();
for (int i = 0; i < urlList.size() ; i++) {
String[] idx_url = new String[2];
// 图片下标
idx_url[0]=i+"";
// 拼接url
idx_url[1] = urlList.get(i);
list.add(idx_url);
}
return list;
}
/**
* 文件上传
* @param files 需要上传的文件
* @return 文件url
* @author YSL
* 2019-03-01 17:14
*/
@RequestMapping("/file_upload")
@ResponseBody
public List<String> fileUpload(@RequestParam(value = "files", required = false) MultipartFile[] files, HttpServletRequest request){
List<String> urlList = upload(files, "pictures", request);
return urlList;
}
/**
* 文件/图片上传。并做备份<br/>
* 路径不能有反斜线和空格 <br/>
* 上传路径:.../webapps/blog_files/pictures/20190301/图片 <br/>
* 上传路径:.../webapps/blog_files/files/20190301/文件 <br/>
* 备份路径:.../webserver_bak/blog/pictures/20190301/图片 <br/>
* 备份路径:.../webserver_bak/blog/files/20190301/文件
* @param files 需要上传的文件
* @param categoryPath 类别路径,pictures/files
* @return 上传成功,返回文件url
* @author YSL
* 2019-03-01 16:45
*/
public List<String> upload(MultipartFile[] files, String categoryPath, HttpServletRequest request){
// 非空判定
if(files == null || files.length == 0){
return new ArrayList<>();
}
// 专门存放文件工程名称(是一个javaweb工程,方便图片直接通过http访问)
String fileProject = "blog_files";
// 备份路径
String bakPath = "D:/webserver_bak/blog/";
//http://localhost:7989/
String ipPort = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort() + "/";
/**
* 获取项目绝对路径,格式,D:\tomcats\apache-tomcat-8.0.52\webapps\boot\。
* markdown编辑器图片路径不能有\,所以替换为/
* 注意:.replace("//", "/"); 与 replaceAll("\\\\", "/");
*/
String rootPath = request.getSession().getServletContext().getRealPath("").replaceAll("\\\\", "/");
// 项目路径。/boot
String contextPath = request.getContextPath();
rootPath = rootPath.substring(0, rootPath.lastIndexOf(contextPath.replace("/","")));
StringBuilder fileRoot = new StringBuilder("");
// 工程名称
fileRoot.append(fileProject);
fileRoot.append("/");
// 类别目录
fileRoot.append(categoryPath);
fileRoot.append("/");
// 文件目录,图片上传失败时使用
String picRootPath = fileRoot.toString();
String day = new SimpleDateFormat("yyyyMMdd").format(new Date());
// 日期目录
fileRoot.append(day);
fileRoot.append("/");
// 文件最终保存目录
String fileDir = fileRoot.toString();
List<String> list = new ArrayList<>();
for (MultipartFile multipartFile : files) {
// 文件名称。markdown编辑器图片路径不能有空格
String upFileName = multipartFile.getOriginalFilename().replaceAll("\\s+", "");
String filename = new SimpleDateFormat("HHmmss").format(new Date()) + "_" + UUID.randomUUID().toString() + "_" + upFileName;
String filePathName = rootPath + fileDir + filename;
File destFile = new File(filePathName);
try {
// 复制临时文件到指定目录下, 会创建没有的目录
FileUtils.copyInputStreamToFile(multipartFile.getInputStream(), destFile);
// 拼接url
list.add(ipPort + fileDir + filename);
// 备份
File bakFile = new File(bakPath + fileDir + filename);
FileUtils.copyInputStreamToFile(multipartFile.getInputStream(), bakFile);
} catch (UnsupportedEncodingException e2) {
e2.printStackTrace();
if("pictures".equals(categoryPath)){
// 默认图片
list.add(picRootPath+"default.jpg");
}else{
list.add("");
}
} catch (IOException e) {
e.printStackTrace();
if("pictures".equals(categoryPath)){
// 默认图片
list.add(picRootPath+"default.jpg");
}else{
list.add("");
}
}
}
return list;
}
}
数据库
字段名 | 类型 | 长度 | 备注 |
---|---|---|---|
id | int | 默认 | 文章id |
markdown | text | markdown格式内容 | |
html | text | html格式内容 |
参考
https://blog.csdn.net/qq_32407233/article/details/84656914
https://blog.csdn.net/wangjun5159/article/details/48809427
https://segmentfault.com/q/1010000016563395
更多推荐
所有评论(0)