Skip to content

Commit 0510b51

Browse files
committed
supported Picture in posts
1 parent 2dd0643 commit 0510b51

File tree

10 files changed

+594
-96
lines changed

10 files changed

+594
-96
lines changed
Lines changed: 383 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,383 @@
1+
# 帖子发图功能
2+
3+
之前的懒不能偷。。现在应测试需求补上发帖时的图片功能
4+
5+
修改 publish 和 update 界面的逻辑,增加存储图片的逻辑
6+
7+
[参考](https://itmtx.cn/article/199?columnId=12)
8+
9+
10+
11+
1. SecurityConfig新增跨域解决:
12+
13+
```java
14+
@Bean
15+
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
16+
// 禁用X-Frame-Options
17+
http.headers(headers -> headers
18+
.frameOptions(frameOptions -> frameOptions.disable())
19+
);
20+
}
21+
```
22+
23+
2. CommunityUtil新返回json接口:
24+
25+
```java
26+
// editor.md 要求返回的 JSON 字符串格式
27+
public static String getEditorMdJSONString(int success, String message, String url) {
28+
JSONObject json = new JSONObject();
29+
json.put("success", success);
30+
json.put("message", message);
31+
json.put("url", url);
32+
return json.toJSONString();
33+
}
34+
```
35+
36+
3. DiscussPostController新增:
37+
38+
在这之前,先配置路径:
39+
40+
develop:
41+
42+
```
43+
community.path.editormdUploadPath=c:/Users/15170/Desktop/community/data
44+
```
45+
46+
produce:
47+
48+
```
49+
community.path.editormdUploadPath=/tmp/uploads/mdPic
50+
```
51+
52+
```java
53+
// 处理帖子上传图片
54+
@RequestMapping(path = "/uploadMdPic", method = RequestMethod.POST)
55+
@ResponseBody
56+
public String uploadMdPic(@RequestParam(value = "editormd-image-file", required = false) MultipartFile file) {
57+
58+
String url = null; // 图片访问地址
59+
try {
60+
// 获取上传文件的名称
61+
String trueFileName = file.getOriginalFilename();
62+
String suffix = trueFileName.substring(trueFileName.lastIndexOf("."));
63+
String fileName = CommunityUtil.genUUID() + suffix;
64+
65+
// 图片存储路径
66+
File dest = new File(editormdUploadPath + "/" + fileName);
67+
if (!dest.getParentFile().exists()) {
68+
dest.getParentFile().mkdirs();
69+
}
70+
71+
// 保存图片到存储路径
72+
file.transferTo(dest);
73+
74+
// 图片访问地址
75+
url = domain + contextPath + "/upload/" + fileName;
76+
System.out.println(url);
77+
} catch (Exception e) {
78+
e.printStackTrace();
79+
return CommunityUtil.getEditorMdJSONString(0, "上传失败", url);
80+
}
81+
return CommunityUtil.getEditorMdJSONString(1, "上传成功", url);
82+
}
83+
84+
// 帖子读取图片
85+
@RequestMapping(path = "/upload/{fileName}", method = RequestMethod.GET)
86+
public void getMdPic(@PathVariable("fileName") String fileName, HttpServletResponse response) {
87+
// 服务器存放路径
88+
String filePath = editormdUploadPath + "/" + fileName;
89+
90+
File file = new File(filePath);
91+
if (!file.exists()) {
92+
System.out.println("文件不存在: " + filePath);
93+
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
94+
return;
95+
}
96+
97+
String suffix = fileName.substring(fileName.lastIndexOf(".") + 1);
98+
99+
// 响应文件
100+
response.setContentType("image/" + suffix);
101+
try (
102+
OutputStream os = response.getOutputStream();
103+
FileInputStream fis = new FileInputStream(file);
104+
) {
105+
byte[] buffer = new byte[1024];
106+
int b;
107+
while ((b = fis.read(buffer)) != -1) {
108+
os.write(buffer, 0, b);
109+
}
110+
} catch (IOException e) {
111+
e.printStackTrace();
112+
}
113+
}
114+
```
115+
116+
3. 前端
117+
118+
```html
119+
<script type="text/javascript">
120+
var testEditor;
121+
122+
$(function() {
123+
testEditor = editormd("test-editormd", {
124+
width: "90%",
125+
height: 640,
126+
syncScrolling: "single",
127+
path: "../editor-md/lib/",
128+
saveHTMLToTextarea: true, // 方便post提交表单
129+
placeholder: "欢迎来到帖子发布界面~ 本论坛支持 Markdown/非Markdown 格式的帖子~",
130+
// 上传图片
131+
imageUpload : true,
132+
imageFormats : ["jpg", "jpeg", "gif", "png", "bmp", "webp"],
133+
imageUploadURL : CONTEXT_PATH + "/discuss/uploadMdPic", // 后端上传图片的服务地址
134+
onload : function() {}
135+
});
136+
</script>
137+
```
138+
139+
注意:这里需要修改官方文件的/plugins/image-dialog/image-dialog.js, 参考[博客](https://blog.csdn.net/ELOVINFG/article/details/103048813)
140+
141+
如下:
142+
143+
```javascript
144+
(function() {
145+
146+
var factory = function (exports) {
147+
148+
var pluginName = "image-dialog";
149+
150+
exports.fn.imageDialog = function() {
151+
152+
var _this = this;
153+
var cm = this.cm;
154+
var lang = this.lang;
155+
var editor = this.editor;
156+
var settings = this.settings;
157+
var cursor = cm.getCursor();
158+
var selection = cm.getSelection();
159+
var imageLang = lang.dialog.image;
160+
var classPrefix = this.classPrefix;
161+
var iframeName = classPrefix + "image-iframe";
162+
var dialogName = classPrefix + pluginName, dialog;
163+
164+
cm.focus();
165+
166+
var loading = function(show) {
167+
var _loading = dialog.find("." + classPrefix + "dialog-mask");
168+
_loading[(show) ? "show" : "hide"]();
169+
};
170+
171+
if (editor.find("." + dialogName).length < 1)
172+
{
173+
var guid = (new Date).getTime();
174+
var action = settings.imageUploadURL + (settings.imageUploadURL.indexOf("?") >= 0 ? "&" : "?") + "guid=" + guid;
175+
176+
if (settings.crossDomainUpload)
177+
{
178+
action += "&callback=" + settings.uploadCallbackURL + "&dialog_id=editormd-image-dialog-" + guid;
179+
}
180+
var dialogContent = ( (settings.imageUpload) ? "<form action=\"#\" target=\"" + iframeName + "\" method=\"post\" enctype=\"multipart/form-data\" class=\"" + classPrefix + "form\">" : "<div class=\"" + classPrefix + "form\">" ) +
181+
( (settings.imageUpload) ? "<iframe name=\"" + iframeName + "\" id=\"" + iframeName + "\" guid=\"" + guid + "\"></iframe>" : "" ) +
182+
"<label>" + imageLang.url + "</label>" +
183+
"<input type=\"text\" data-url />" + (function(){
184+
return (settings.imageUpload) ? "<div class=\"" + classPrefix + "file-input\">" +
185+
"<input type=\"file\" name=\"" + classPrefix + "image-file\" id=\"" + classPrefix + "image-file\" accept=\"image/*\" />" +
186+
"<input type=\"submit\" value=\"" + imageLang.uploadButton + "\" />" +
187+
"</div>" : "";
188+
})() +
189+
"<br/>" +
190+
"<label>" + imageLang.alt + "</label>" +
191+
"<input type=\"text\" value=\"" + selection + "\" data-alt />" +
192+
"<br/>" +
193+
"<label>" + imageLang.link + "</label>" +
194+
"<input type=\"text\" value=\"http://\" data-link />" +
195+
"<br/>" +
196+
( (settings.imageUpload) ? "</form>" : "</div>");
197+
198+
dialog = this.createDialog({
199+
title : imageLang.title,
200+
width : (settings.imageUpload) ? 465 : 380,
201+
height : 254,
202+
name : dialogName,
203+
content : dialogContent,
204+
mask : settings.dialogShowMask,
205+
drag : settings.dialogDraggable,
206+
lockScreen : settings.dialogLockScreen,
207+
maskStyle : {
208+
opacity : settings.dialogMaskOpacity,
209+
backgroundColor : settings.dialogMaskBgColor
210+
},
211+
buttons : {
212+
enter : [lang.buttons.enter, function() {
213+
var url = this.find("[data-url]").val();
214+
var alt = this.find("[data-alt]").val();
215+
var link = this.find("[data-link]").val();
216+
217+
if (url === "")
218+
{
219+
alert(imageLang.imageURLEmpty);
220+
return false;
221+
}
222+
223+
var altAttr = (alt !== "") ? " \"" + alt + "\"" : "";
224+
225+
if (link === "" || link === "http://")
226+
{
227+
cm.replaceSelection("![" + alt + "](" + url + altAttr + ")");
228+
}
229+
else
230+
{
231+
cm.replaceSelection("[![" + alt + "](" + url + altAttr + ")](" + link + altAttr + ")");
232+
}
233+
234+
if (alt === "") {
235+
cm.setCursor(cursor.line, cursor.ch + 2);
236+
}
237+
238+
this.hide().lockScreen(false).hideMask();
239+
240+
//删除对话框
241+
this.remove();
242+
243+
return false;
244+
}],
245+
246+
cancel : [lang.buttons.cancel, function() {
247+
this.hide().lockScreen(false).hideMask();
248+
249+
//删除对话框
250+
this.remove();
251+
252+
return false;
253+
}]
254+
}
255+
});
256+
257+
dialog.attr("id", classPrefix + "image-dialog-" + guid);
258+
259+
if (!settings.imageUpload) {
260+
return ;
261+
}
262+
263+
var fileInput = dialog.find("[name=\"" + classPrefix + "image-file\"]");
264+
265+
fileInput.bind("change", function() {
266+
var fileName = fileInput.val();
267+
var isImage = new RegExp("(\\.(" + settings.imageFormats.join("|") + "))$", "i"); // /(\.(webp|jpg|jpeg|gif|bmp|png))$/
268+
269+
if (fileName === "")
270+
{
271+
alert(imageLang.uploadFileEmpty);
272+
273+
return false;
274+
}
275+
276+
if (!isImage.test(fileName))
277+
{
278+
alert(imageLang.formatNotAllowed + settings.imageFormats.join(", "));
279+
280+
return false;
281+
}
282+
283+
loading(true);
284+
285+
var submitHandler = function() {
286+
287+
288+
var uploadIframe = document.getElementById(iframeName);
289+
290+
uploadIframe.onload = function() {
291+
292+
loading(false);
293+
294+
var formData = new FormData();
295+
formData.append("editormd-image-file",$("#editormd-image-file")[0].files[0]);
296+
var action = settings.imageUploadURL + (settings.imageUploadURL.indexOf("?") >= 0 ? "&" : "?") + "guid=" + guid;
297+
let token = $("meta[name= '_csrf']").attr("content");
298+
let header = $("meta[name= '_csrf_header']").attr("content");
299+
$(document).ajaxSend(function (e, xhr, options){
300+
xhr.setRequestHeader(header, token);
301+
});
302+
$.ajax({
303+
type:"post",
304+
url:action,
305+
data:formData,
306+
dataType:"json",
307+
async:false,
308+
processData : false, // 使数据不做处理
309+
contentType : false, // 不要设置Content-Type请求头
310+
success:function(data){
311+
// 成功拿到结果放到这个函数 data就是拿到的结果
312+
console.log(data);
313+
if(data.success == 1){
314+
console.log(data.message);
315+
dialog.find("[data-url]").val(data.url);
316+
}else{
317+
alert(data.message);
318+
}
319+
},
320+
});
321+
322+
return false;
323+
};
324+
};
325+
326+
dialog.find("[type=\"submit\"]").bind("click", submitHandler).trigger("click");
327+
});
328+
}
329+
330+
dialog = editor.find("." + dialogName);
331+
dialog.find("[type=\"text\"]").val("");
332+
dialog.find("[type=\"file\"]").val("");
333+
dialog.find("[data-link]").val("http://");
334+
335+
this.dialogShowMask(dialog);
336+
this.dialogLockScreen();
337+
dialog.show();
338+
339+
};
340+
341+
};
342+
343+
// CommonJS/Node.js
344+
if (typeof require === "function" && typeof exports === "object" && typeof module === "object")
345+
{
346+
module.exports = factory;
347+
}
348+
else if (typeof define === "function") // AMD/CMD/Sea.js
349+
{
350+
if (define.amd) { // for Require.js
351+
352+
define(["editormd"], function(editormd) {
353+
factory(editormd);
354+
});
355+
356+
} else { // for Sea.js
357+
define(function(require) {
358+
var editormd = require("./../../editormd");
359+
factory(editormd);
360+
});
361+
}
362+
}
363+
else
364+
{
365+
factory(window.editormd);
366+
}
367+
})();
368+
```
369+
370+
注意:如果一直显示图片404,应该是虚拟路径映射的问题,springboot为了保护服务器,会隐藏真实路径
371+
372+
只要在WebMvcConfig下增加:
373+
374+
```java
375+
@Value("${community.path.editormdUploadPath}")
376+
private String editormdUploadPath;
377+
378+
@Override
379+
public void addResourceHandlers(ResourceHandlerRegistry registry) {
380+
// 将上传路径映射到虚拟路径
381+
registry.addResourceHandler("/upload/**").addResourceLocations("file:" + editormdUploadPath + "/");
382+
}
383+
```

0 commit comments

Comments
 (0)