SpringBoot+Vue整合MarkDown上传图片
前端代码
在main.js引入mavenEditor
import Vue from 'vue'
import mavonEditor from 'mavon-editor'
import 'mavon-editor/dist/css/index.css'
Vue.use(mavonEditor)
mavon-Editor组件代码
<mavon-editor
v-model="form.content"
class="md"
ref="md"
@imgAdd="imgAdd"
/>
imgAdd(pos, $file) {
let formdata = new FormData()
formdata.append('pic', $file)
axios({
headers: {'Content-Type': 'multipart/form-data',},// 设置传输内容的类型和编码
withCredentials: true,// 指定某个请求应该发送凭据
url: '/apis/moment/upload',
method: 'POST',
data: formdata
}).then(res => {
this.$refs.md.$img2Url(pos, res.data.data);
});
},
后端代码
文件上传配置
spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 100MB
nginx本地映射配置
upload:
mode: local
local:
# nginx映射本地文件路径
url: http://localhost/
# 本地文件存储路径
path: /upload/
nginx配置单次文件最大上传大小(位置放在http{ }中)
client_max_body_size 10m;
controller层代码
/**
* markdown上传文章正文图片
*/
@PostMapping("/blogs/upload")
public R<String> upload(@RequestParam("pic") MultipartFile file) {
return R.ok(uploadStrategyContext.executeUploadStrategy(file, FilePathEnum.ARTICLE.getPath()));
}
接口返回类
/**
* 接口返回类
*/
@Data
public class R<T> {
/**
* 返回状态
*/
private Boolean flag;
/**
* 返回码
*/
private Integer code;
/**
* 返回信息
*/
private String message;
/**
* 返回数据
*/
private T data;
public static <T> R<T> ok() {
return restResult(true, null, SUCCESS.getCode(), SUCCESS.getDesc());
}
public static <T> R<T> ok(T data) {
return restResult(true, data, SUCCESS.getCode(), SUCCESS.getDesc());
}
public static <T> R<T> ok(T data, String message) {
return restResult(true, data, SUCCESS.getCode(), message);
}
public static <T> R<T> fail() {
return restResult(false, null, FAIL.getCode(), FAIL.getDesc());
}
public static <T> R<T> fail(StatusCodeEnum statusCodeEnum) {
return restResult(false, null, statusCodeEnum.getCode(), statusCodeEnum.getDesc());
}
public static <T> R<T> fail(String message) {
return restResult(message);
}
public static <T> R<T> fail(T data) {
return restResult(false, data, FAIL.getCode(), FAIL.getDesc());
}
public static <T> R<T> fail(T data, String message) {
return restResult(false, data, FAIL.getCode(), message);
}
public static <T> R<T> fail(Integer code, String message) {
return restResult(false, null, code, message);
}
private static <T> R<T> restResult(String message) {
R<T> apiResult = new R<>();
apiResult.setFlag(false);
apiResult.setCode(FAIL.getCode());
apiResult.setMessage(message);
return apiResult;
}
private static <T> R<T> restResult(Boolean flag, T data, Integer code, String message) {
R<T> apiResult = new R<>();
apiResult.setFlag(flag);
apiResult.setData(data);
apiResult.setCode(code);
apiResult.setMessage(message);
return apiResult;
}
}
上传策略接口
public interface UploadStrategy {
/**
* 上传文件
*
* @param file 文件
* @param path 上传路径
* @return {@link String} 文件地址
*/
String uploadFile(MultipartFile file, String path);
}
上传策略上下文处理类
@Service
public class UploadStrategyContext {
/**
* 上传模式
*/
@Value("${upload.mode}")
private String uploadMode;
@Resource
private Map<String, UploadStrategy> uploadStrategyMap;
/**
* 执行上传策略
*
* @param file 文件
* @param path 路径
* @return {@link String} 文件地址
*/
public String executeUploadStrategy(MultipartFile file, String path) {
return uploadStrategyMap.get(getStrategy(uploadMode)).uploadFile(file, path);
}
}
抽象上传模板实现类
@Service
public abstract class AbstractUploadStrategyImpl implements UploadStrategy {
@Override
public String uploadFile(MultipartFile file, String path) {
try {
// 获取文件md5值
String md5 = FileUtils.getMd5(file.getInputStream());
// 获取文件扩展名
String extName = FileUtils.getExtName(file.getOriginalFilename());
// 重新生成文件名
String fileName = md5 + extName;
// 判断文件是否已存在
if (!exists(fileName)) {
// 不存在则继续上传
upload(path, fileName, file.getInputStream());
}
// 返回文件访问路径
return getFileAccessUrl(path + fileName);
} catch (Exception e) {
e.printStackTrace();
throw new BizException("文件上传失败");
}
}
/**
* 判断文件是否存在
*
* @param filePath 文件路径
* @return {@link Boolean}
*/
public abstract Boolean exists(String filePath);
/**
* 上传
*
* @param path 路径
* @param fileName 文件名
* @param inputStream 输入流
* @throws IOException io异常
*/
public abstract void upload(String path, String fileName, InputStream inputStream) throws IOException;
/**
* 获取文件访问url
*
* @param filePath 文件路径
* @return {@link String}
*/
public abstract String getFileAccessUrl(String filePath);
}
本地上传策略实现类
@Service("localUploadStrategyImpl")
public class LocalUploadStrategyImpl extends AbstractUploadStrategyImpl {
/**
* 本地路径
*/
@Value("${upload.local.path}")
private String localPath;
/**
* 访问url
*/
@Value("${upload.local.url}")
private String localUrl;
@Override
public Boolean exists(String filePath) {
return new File(localPath + filePath).exists();
}
@Override
public void upload(String path, String fileName, InputStream inputStream) throws IOException {
// 判断目录是否存在
File directory = new File(localPath + path);
if (!directory.exists()) {
if (!directory.mkdirs()) {
throw new BizException("创建目录失败");
}
}
// 写入文件
File file = new File(localPath + path + fileName);
String ext = "." + fileName.split("\\.")[1];
switch (Objects.requireNonNull(FileExtEnum.getFileExt(ext))) {
case MD, TXT -> {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
BufferedWriter writer = new BufferedWriter(new FileWriter(file));
while (reader.ready()) {
writer.write((char) reader.read());
}
writer.flush();
writer.close();
reader.close();
}
default -> {
BufferedInputStream bis = new BufferedInputStream(inputStream);
BufferedOutputStream bos = new BufferedOutputStream(Files.newOutputStream(file.toPath()));
byte[] bytes = new byte[1024];
int length;
while ((length = bis.read(bytes)) != -1) {
bos.write(bytes, 0, length);
}
bos.flush();
bos.close();
bis.close();
}
}
inputStream.close();
}
@Override
public String getFileAccessUrl(String filePath) {
return localUrl + filePath;
}
}
异常处理类
@Getter
@AllArgsConstructor
public class BizException extends RuntimeException {
/**
* 错误码
*/
private Integer code = FAIL.getCode();
/**
* 错误信息
*/
private final String message;
public BizException(String message) {
this.message = message;
}
public BizException(StatusCodeEnum statusCodeEnum) {
this.code = statusCodeEnum.getCode();
this.message = statusCodeEnum.getDesc();
}
}
文件扩展名枚举类
@Getter
@AllArgsConstructor
public enum FileExtEnum {
/**
* jpg文件
*/
JPG(".jpg", "jpg文件"),
/**
* png文件
*/
PNG(".png", "png文件"),
/**
* Jpeg文件
*/
JPEG(".jpeg", "jpeg文件"),
/**
* wav文件
*/
WAV(".wav", "wav文件"),
/**
* md文件
*/
MD(".md","markdown文件"),
/**
* txt文件
*/
TXT(".txt","txt文件");
/**
* 获取文件格式
*
* @param extName 扩展名
* @return {@link FileExtEnum} 文件格式
*/
public static FileExtEnum getFileExt(String extName) {
for (FileExtEnum value : FileExtEnum.values()) {
if (value.getExtName().equalsIgnoreCase(extName)) {
return value;
}
}
return null;
}
/**
* 扩展名
*/
private final String extName;
/**
* 描述
*/
private final String desc;
}
文件路径枚举类
@Getter
@AllArgsConstructor
public enum FilePathEnum {
/**
* 头像路径
*/
AVATAR("avatar/", "头像路径"),
/**
* 文章图片路径
*/
ARTICLE("articles/", "文章图片路径"),
/**
* 音频路径
*/
VOICE("voice/", "音频路径"),
/**
* 照片路径
*/
PHOTO("photos/", "相册路径"),
/**
* 配置图片路径
*/
CONFIG("config/", "配置图片路径"),
/**
* 说说图片路径
*/
TALK("talks/", "说说图片路径"),
/**
* md文件路径
*/
MD("markdown/", "md文件路径");
/**
* 路径
*/
private final String path;
/**
* 描述
*/
private final String desc;
}
接口状态码枚举类
@Getter
@AllArgsConstructor
public enum StatusCodeEnum {
/**
* 成功
*/
SUCCESS(20000, "操作成功"),
/**
* 未登录
*/
NO_LOGIN(40001, "用户未登录"),
/**
* 没有操作权限
*/
AUTHORIZED(40300, "没有操作权限"),
/**
* 系统异常
*/
SYSTEM_ERROR(50000, "系统异常"),
/**
* 失败
*/
FAIL(51000, "操作失败"),
/**
* 参数校验失败
*/
VALID_ERROR(52000, "参数格式不正确"),
/**
* 用户名已存在
*/
USERNAME_EXIST(52001, "用户名已存在"),
/**
* 用户名不存在
*/
USERNAME_NOT_EXIST(52002, "用户名不存在"),
/**
* qq登录错误
*/
QQ_LOGIN_ERROR(53001, "qq登录错误"),
/**
* 微博登录错误
*/
WEIBO_LOGIN_ERROR(53002, "微博登录错误");
/**
* 状态码
*/
private final Integer code;
/**
* 描述
*/
private final String desc;
}
上传模式枚举类
@Getter
@AllArgsConstructor
public enum UploadModeEnum {
/**
* oss
*/
OSS("oss", "ossUploadStrategyImpl"),
/**
* 本地
*/
LOCAL("local", "localUploadStrategyImpl"),
/**
* cos
*/
COS("cos", "cosUploadStrategyImpl");
/**
* 模式
*/
private final String mode;
/**
* 策略
*/
private final String strategy;
/**
* 获取策略
*
* @param mode 模式
* @return {@link String} 搜索策略
*/
public static String getStrategy(String mode) {
for (UploadModeEnum value : UploadModeEnum.values()) {
if (value.getMode().equals(mode)) {
return value.getStrategy();
}
}
return null;
}
}