|
| 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(""); |
| 228 | + } |
| 229 | + else |
| 230 | + { |
| 231 | + cm.replaceSelection("[](" + 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