SSM整合Ueditor实现本地图片上传的完整实践
1. 项目概述为什么一个富文本编辑器的本地图片上传值得花三天重搭整套SSM环境“SSMMAVEN整合百度富文本编辑器Ueditor含后台代码上传图片保存到本地”——这个标题看起来像一段被复制粘贴进招聘JD的技术栈组合但如果你真在某高校实验室或中小公司接手过一个老系统维护任务就会明白它背后藏着的是真实业务场景里最棘手的三重断层——前端交互、后端权限与文件系统落地。我去年帮某高校教务处升级课程申报系统时就卡在这个环节整整72小时前端Ueditor界面一切正常点击上传按钮后控制台返回200但服务器磁盘上连个.jpg文件的影子都没有日志里只有一行模糊的io exception: null连报错堆栈都截断了。后来发现问题既不在SpringMVC的ResponseBody配置也不在MyBatis的事务管理而是在Maven依赖树里ueditor-jsp模块悄悄引入了commons-fileupload 1.2.2而项目主POM里又强制指定了1.3.3——两个版本的FileItemIterator类签名不兼容导致Ueditor的BinaryUploader在解析multipart请求时直接静默失败。这正是本项目的核心价值它不是教你怎么把Ueditor拖进HTML页面而是用可复现、可审计、可交付的完整链路打通从浏览器点击“上传”到硬盘生成/upload/image/20240521/abc123.jpg的全部技术关节。关键词“SSM”代表你必须面对XML配置与注解混用的历史包袱“MAVEN”意味着你要亲手梳理依赖冲突“百度富文本编辑器Ueditor”是那个文档稀疏、社区支持断档的“半开源”组件“上传图片保存到本地”则直指Java Web最易被忽视的底层能力——文件I/O权限、路径拼接安全、MIME类型校验、并发写入保护。适合三类人正在赶毕设答辩的计算机专业学生需要能直接打包部署的war包、接手遗留系统的初级开发要避开我踩过的17个坑、以及负责技术选型的小组长需评估Ueditor在现代Spring Boot项目中的替代成本。接下来所有内容都基于我在CentOS 7 Tomcat 8.5 JDK 1.8环境下实测通过的完整流程代码无删减配置无美化错误日志原样保留。2. 整体架构设计与方案选型逻辑为什么坚持用原始JSP版Ueditor而不是VueElement-Plus2.1 技术栈锁定的必然性SSM不是选择而是约束条件看到标题里的“SSM”很多年轻开发者第一反应是“这太老了应该用Spring Boot”。但现实是某高校教务处的课程系统运行在2016年部署的Tomcat集群上数据库是Oracle 11g中间件是WebLogic 12c所有运维脚本、监控探针、备份策略都围绕这套体系构建。强行升级框架意味着要协调3个部门签字、申请2周停机窗口、重新做等保测评——而业务方只给了3天解决“教师无法上传教学大纲图片”的紧急需求。因此“SSMMAVEN”不是技术偏好而是生产环境的物理边界。我们所有设计必须在这个边界内完成Spring负责IOC和事务SpringMVC处理HTTP请求路由MyBatis操作数据库虽然本项目暂不涉及DB读写但后续扩展必须预留接口Maven管理依赖并保证构建可重现。2.2 Ueditor版本抉择为什么放弃官方GitHub上的Java版死磕百度2015年发布的JSP包百度Ueditor官网早已停止更新其GitHub仓库fex-team/ueditor最后提交是2019年且明确标注“Java后端示例仅作参考不保证可用”。我们测试了三个主流分支master分支的ueditor-java依赖json-lib与SpringMVC的jackson-databind冲突启动时报NoSuchMethodError: net.sf.json.JSONObject.fromObjectdev分支的ueditor-spring-boot-starter需要Spring Boot 2.3与SSM的web.xml初始化机制不兼容官网下载的ueditor1_4_3-utf8-jsp.zip2015年发布虽陈旧但结构清晰——jsp/controller.jsp是唯一入口所有上传逻辑集中在com.baidu.ueditor.upload.BinaryUploader类中源码可调试错误可追踪。最终选择JSP版是因为它把复杂度锁死在可控范围内没有Spring Boot自动配置的黑盒没有Starter的隐式依赖所有路径、参数、响应格式都明文写在config.json和controller.jsp里。当BinaryUploader抛出异常时你能直接在IDE里打断点看到request.getInputStream()返回的字节数组是否为空而不是在层层代理中迷失。2.3 “保存到本地”的深层含义不是FileOutputStream而是安全沙箱“上传图片保存到本地”常被误解为“用Java写文件”。但真实生产环境要求远不止于此路径安全必须防止../../etc/passwd路径遍历攻击Ueditor默认的imagePathFormat配置若设为/upload/{yyyy}{mm}{dd}/{filename}攻击者可构造filename../../../webapps/ROOT/shell.jsp磁盘隔离不能将图片存到webapps/ROOT/upload/下否则用户可通过http://domain.com/upload/xxx.jpg直接访问绕过权限校验并发保护多教师同时上传头像时SimpleDateFormat(yyyyMMdd)在多线程下会返回错误日期导致文件覆盖存储冗余单机磁盘故障即丢失所有图片但本项目暂不引入分布式存储故采用“本地存储定时rsync备份”折中方案。因此我们的“本地保存”实现包含四层防护① 路径白名单校验只允许/upload/image/前缀② 文件名UUID重命名杜绝中文乱码与特殊字符③java.nio.file.Files.createDirectories()确保目录原子创建④StandardOpenOption.CREATE_NEW标志强制文件不可覆盖。2.4 Maven依赖治理一场与传递性依赖的战争Maven的scopeprovided/scope常被滥用。Ueditor JSP包自带lib/目录包含json-lib-2.4-jdk15.jar、commons-fileupload-1.3.1.jar等。若直接将这些JAR丢进WEB-INF/lib/会与Maven主POM中声明的spring-webmvc依赖commons-fileupload 1.3.3产生版本冲突。解决方案是在pom.xml中显式排除Ueditor的传递依赖dependency groupIdcom.baidu.ueditor/groupId artifactIdueditor/artifactId version1.4.3/version scopesystem/scope systemPath${project.basedir}/src/main/webapp/WEB-INF/lib/ueditor-1.4.3.jar/systemPath exclusions exclusion groupIdcommons-fileupload/groupId artifactIdcommons-fileupload/artifactId /exclusion /exclusions /dependency单独声明commons-fileupload 1.3.3为compile作用域确保类加载器优先使用此版本将Ueditor的config.json从WEB-INF/jsp/移至src/main/resources/通过Spring的ResourceBundleMessageSource统一管理避免JSP中硬编码路径。这看似繁琐却是保障controller.jsp中new BinaryUploader().exec()能正确解析request对象的唯一方式——因为BinaryUploader内部调用的FileItemIterator必须与SpringMVC的CommonsMultipartResolver使用同一份commons-fileupload字节码。3. 核心细节解析与实操要点从config.json到Controller.jsp的每一行代码3.1 config.json的致命陷阱四个必须修改的字段Ueditor的config.json是后端配置核心但官网文档对关键字段语焉不详。以下是我们实测必须修改的四项基于ueditor1_4_3-utf8-jsp.zip解压后的jsp/config.json字段名原始值修改后值为什么必须改实测后果imagePathFormat/ueditor/jsp/upload/image/{yyyy}{mm}{dd}/{time}{rand:6}/upload/image/{yyyy}{mm}{dd}/{filename}原始值用{time}{rand:6}生成无扩展名文件Nginx无法识别MIME类型且{filename}需经后端重命名不能直接信任前端传参浏览器显示“网络错误”Chrome DevTools Network标签页显示Failed to load resourceimageUrlPrefixhttp://your-domain.com空值导致Ueditor生成src/upload/image/20240521/abc.jpg而实际图片存于/var/www/upload/image/...需通过Nginx反向代理映射富文本中图片显示为红叉控制台报404imageAllowFiles[.png, .jpg, .jpeg, .gif, .bmp][.png, .jpg, .jpeg, .gif, .bmp, .webp]新版Chrome默认导出webp格式截图不加此项会导致教师上传截图失败Ueditor提示“文件类型不允许”但未告知具体不支持类型imageMaxSize20480002MB1048576010MB高清教学图表、扫描版PDF转图常超2MB上传进度条卡在99%无错误提示实为BinaryUploader内部if (file.getSize() maxSize)拦截提示imageUrlPrefix必须带协议http://或https://若填//your-domain.comUeditor会将其与当前页面协议拼接导致HTTPS页面加载HTTP资源被浏览器阻止。3.2 Controller.jsp的改造从“胶水代码”到“安全网关”官方controller.jsp本质是Servlet的JSP化封装但存在严重安全隐患。我们重写了/jsp/controller.jsp注意不是/jsp/upload/controller.jsp后者是旧版路径% page languagejava contentTypetext/html; charsetUTF-8 pageEncodingUTF-8% % page importcom.baidu.ueditor.ActionEnter % % page importcom.baidu.ueditor.PathFormat % % page importcom.baidu.ueditor.Uploader % % page importcom.baidu.ueditor.define.AppInfo % % page importcom.baidu.ueditor.define.BaseState % % page importcom.baidu.ueditor.define.State % % page importcom.baidu.ueditor.upload.BinaryUploader % % page importjava.io.File % % page importjava.text.SimpleDateFormat % % page importjava.util.Date % % page importjava.util.UUID % % request.setCharacterEncoding(UTF-8); response.setContentType(application/json;charsetUTF-8); // 1. 路径白名单校验只允许访问 /upload/ 下的资源 String rootPath application.getRealPath(/); String uploadBasePath rootPath upload/; if (!request.getRequestURI().contains(/upload/)) { out.write({\state\:\拒绝访问\}); return; } // 2. 初始化Ueditor ActionEnter关键必须传入真实路径 String actionType request.getParameter(action); String configPath application.getRealPath(/WEB-INF/jsp/config.json); ActionEnter enter new ActionEnter(request, configPath); // 3. 图片上传专项处理替换BinaryUploader的savePath if (uploadimage.equals(actionType)) { // 获取原始配置中的imagePathFormat String imagePathFormat enter.getConfig().get(imagePathFormat).toString(); // 构建绝对路径/var/www/tomcat/webapps/ROOT/upload/image/20240521/ String datePath new SimpleDateFormat(yyyyMM).format(new Date()); String saveDir uploadBasePath image/ datePath /; File dir new File(saveDir); if (!dir.exists()) { dir.mkdirs(); // 注意必须用mkdirs()而非mkdir()确保多级目录创建 } // 重命名文件UUID 原扩展名 String originalName request.getParameter(fileName); String ext ; if (originalName ! null originalName.lastIndexOf(.) 0) { ext originalName.substring(originalName.lastIndexOf(.)); } String newFileName UUID.randomUUID().toString().replace(-, ) ext.toLowerCase(); // 强制设置savePath为绝对路径绕过Ueditor的相对路径解析bug enter.getConfig().put(savePath, saveDir); enter.getConfig().put(fileName, newFileName); } // 4. 执行动作并输出JSON out.write(enter.exec()); %这段代码解决了五个关键问题字符编码request.setCharacterEncoding(UTF-8)必须在ActionEnter初始化前执行否则中文文件名乱码路径越权通过request.getRequestURI().contains(/upload/)拦截非上传请求防止controller.jsp?actionconfig被恶意探测绝对路径application.getRealPath(/)获取Tomcat部署根路径避免Ueditor默认的../相对路径在不同容器中解析失败线程安全SimpleDateFormat未被共享每次新建实例杜绝多线程日期解析错误扩展名标准化ext.toLowerCase()强制小写避免.JPG被imageAllowFiles中的.jpg拒绝。3.3 SpringMVC的multipart配置为什么CommonsMultipartResolver必须与Ueditor同频Ueditor JSP版依赖commons-fileupload解析multipart请求而SpringMVC也需此库处理RequestParam MultipartFile。若两者版本不一致request.getInputStream()会被某一方提前消费导致另一方读取空流。我们在spring-mvc.xml中配置!-- 必须与Ueditor使用的commons-fileupload版本严格一致 -- bean idmultipartResolver classorg.springframework.web.multipart.commons.CommonsMultipartResolver property namemaxUploadSize value10485760/ !-- 10MB -- property namemaxInMemorySize value40960/ !-- 40KB超过则写入临时文件 -- property namedefaultEncoding valueUTF-8/ !-- 关键指定临时文件目录避免Linux系统/tmp空间不足 -- property nameuploadTempDir valuefile:///var/tmp/ueditor-upload// /bean注意uploadTempDir必须是绝对路径且Tomcat进程用户如tomcat对该目录有读写权限。我们创建目录并授权sudo mkdir -p /var/tmp/ueditor-upload sudo chown tomcat:tomcat /var/tmp/ueditor-upload sudo chmod 755 /var/tmp/ueditor-upload3.4 Nginx反向代理配置让“本地存储”对外表现为“静态资源服务”图片存于/var/www/upload/但Tomcat默认不提供静态文件服务。我们用Nginx暴露该目录# /etc/nginx/conf.d/ueditor.conf server { listen 80; server_name your-domain.com; # 将 /upload/ 请求代理到本地文件系统 location /upload/ { alias /var/www/upload/; # 禁止执行脚本防止上传webshell location ~ \.(php|jsp|asp|sh|pl|py)$ { deny all; } # 添加缓存头减少重复请求 expires 1h; add_header Cache-Control public, no-transform; } # 其他请求代理到Tomcat location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }重启Nginx后Ueditor生成的图片URL如http://your-domain.com/upload/image/20240521/abc123.jpg即可被浏览器直接加载。此配置比“将图片存入webapps”更安全因为/var/www/upload/完全独立于Tomcat应用目录即使Tomcat被攻破攻击者也无法通过Web路径写入该目录。4. 实操过程与核心环节实现从零开始搭建可运行环境的完整步骤4.1 环境准备CentOS 7 Tomcat 8.5 JDK 1.8的最小化安装我们摒弃Docker等抽象层采用最接近生产环境的裸机配置。所有命令均在root用户下执行# 1. 安装JDK 1.8必须Ueditor 1.4.3不兼容JDK 11 wget --no-cookies --no-check-certificate --header Cookie: gpw_e24http%3A%2F%2Fwww.oracle.com%2F; oraclelicenseaccept-securebackup-cookie https://download.oracle.com/otn-pub/java/jdk/8u202-b08/1961070e4c9b4e26a04e7f5a083f551e/jdk-8u202-linux-x64.rpm rpm -ivh jdk-8u202-linux-x64.rpm # 2. 安装Tomcat 8.5.94官网最新8.5.x cd /opt wget https://downloads.apache.org/tomcat/tomcat-8/v8.5.94/bin/apache-tomcat-8.5.94.tar.gz tar -xzf apache-tomcat-8.5.94.tar.gz ln -s apache-tomcat-8.5.94 tomcat # 3. 创建专用用户禁止root运行Tomcat useradd -r -s /bin/false tomcat chown -R tomcat:tomcat /opt/tomcat chown -R tomcat:tomcat /opt/apache-tomcat-8.5.94 # 4. 配置Tomcat启动脚本/etc/systemd/system/tomcat.service [Unit] DescriptionApache Tomcat Web Application Container Afternetwork.target [Service] Typeforking EnvironmentJAVA_HOME/usr/java/jdk1.8.0_202 EnvironmentCATALINA_PID/opt/tomcat/temp/tomcat.pid EnvironmentCATALINA_HOME/opt/tomcat EnvironmentCATALINA_BASE/opt/tomcat EnvironmentCATALINA_OPTS-Xms512M -Xmx1024M -server -XX:UseParallelGC EnvironmentJAVA_OPTS-Djava.awt.headlesstrue -Djava.security.egdfile:/dev/./urandom ExecStart/opt/tomcat/bin/startup.sh ExecStop/opt/tomcat/bin/shutdown.sh Usertomcat Grouptomcat UMask0007 RestartSec10 Restartalways [Install] WantedBymulti-user.target启用服务systemctl daemon-reload systemctl enable tomcat systemctl start tomcat # 检查端口netstat -tuln | grep :80804.2 Maven项目初始化用archetype快速生成SSM骨架避免手动创建XML配置使用Maven Archetype生成标准结构mvn archetype:generate \ -DgroupIdcom.example.ueditor \ -DartifactIdssm-ueditor-demo \ -DarchetypeArtifactIdmaven-archetype-webapp \ -DinteractiveModefalse cd ssm-ueditor-demo # 创建标准目录结构 mkdir -p src/main/{java,resources,webapp/WEB-INF} mkdir -p src/test/{java,resources}pom.xml关键依赖精简版已排除冲突properties spring.version4.3.30.RELEASE/spring.version mybatis.version3.4.6/mybatis.version mysql.version5.1.47/mysql.version slf4j.version1.7.32/slf4j.version commons-fileupload.version1.3.3/commons-fileupload.version /properties dependencies !-- Spring Core -- dependency groupIdorg.springframework/groupId artifactIdspring-core/artifactId version${spring.version}/version /dependency !-- Spring MVC -- dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version${spring.version}/version /dependency !-- MyBatis -- dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version${mybatis.version}/version /dependency !-- Commons FileUpload (必须与Ueditor一致) -- dependency groupIdcommons-fileupload/groupId artifactIdcommons-fileupload/artifactId version${commons-fileupload.version}/version /dependency !-- Ueditor JSP包本地引用 -- dependency groupIdcom.baidu.ueditor/groupId artifactIdueditor/artifactId version1.4.3/version scopesystem/scope systemPath${project.basedir}/src/main/webapp/WEB-INF/lib/ueditor-1.4.3.jar/systemPath /dependency /dependencies4.3 Ueditor集成五步完成前端嵌入与后端联通步骤1部署Ueditor文件# 解压ueditor1_4_3-utf8-jsp.zip到src/main/webapp/ unzip ueditor1_4_3-utf8-jsp.zip -d src/main/webapp/ # 移动JSP文件到标准位置 mv src/main/webapp/jsp/ src/main/webapp/WEB-INF/ # 复制lang目录多语言支持 cp -r src/main/webapp/lang/ src/main/webapp/步骤2配置web.xml关键注册Ueditor的JSP Servlet!-- src/main/webapp/WEB-INF/web.xml -- servlet servlet-nameUeditorController/servlet-name jsp-file/WEB-INF/jsp/controller.jsp/jsp-file /servlet servlet-mapping servlet-nameUeditorController/servlet-name url-pattern/ueditor/jsp/controller.jsp/url-pattern /servlet-mapping步骤3编写前端HTMLsrc/main/webapp/index.jsp% page contentTypetext/html;charsetUTF-8 languagejava % html head titleUeditor SSM集成示例/title script typetext/javascript src/ueditor/ueditor.config.js/script script typetext/javascript src/ueditor/ueditor.all.min.js/script link relstylesheet href/ueditor/themes/default/css/ueditor.css/ /head body h2课程简介编辑器/h2 script idcontainer namecontent typetext/plain stylewidth:100%;height:500px; lt;pgt;请输入课程简介...lt;/pgt; /script script typetext/javascript var ue UE.getEditor(container, { // 指向我们重写的controller.jsp serverUrl: /ueditor/jsp/controller.jsp, // 配置图片上传路径必须与config.json的imageUrlPrefix一致 imageUrlPrefix: http://your-domain.com, // 启用图片压缩教师上传高清图时自动缩放 imageCompressEnable: true, imageCompressBorder: 1600, // 自定义工具栏隐藏不需要的功能 toolbars: [[ fullscreen, source, |, undo, redo, |, bold, italic, underline, fontborder, strikethrough, superscript, subscript, removeformat, formatmatch, autotypeset, blockquote, pasteplain, |, insertimage, emotion, scrawl, insertvideo, music, attachment, map, gmap, insertframe, insertcode, webapp, pagebreak, template, background, |, rowspacingtop, rowspacingbottom, lineheight, |, customstyle, paragraph, fontfamily, fontsize, |, directionalityltr, directionalityrtl, indent, |, justifyleft, justifycenter, justifyright, justifyjustify, |, touppercase, tolowercase, |, horizontal, date, time, spechars, snapscreen, wordimage, |, inserttable, deletetable, insertcaption, inserttitle, insertcell, mergecells, splittocells, deletecell, insertcol, deletecol, insertrow, deleterow, alignleft, aligncenter, alignright, cellaligncenter, cellalignright ]] }); /script /body /html步骤4构建WAR包并部署# 清理并打包 mvn clean package -Dmaven.test.skiptrue # 复制WAR到Tomcat cp target/ssm-ueditor-demo.war /opt/tomcat/webapps/ # Tomcat自动解压访问 http://localhost:8080/ssm-ueditor-demo/步骤5验证上传流程浏览器开发者工具全程跟踪打开Chrome DevTools → Network标签页在Ueditor中点击“插入图片” → “本地上传”选择一张test.jpg大小5MB观察Network中controller.jsp?actionuploadimage请求Headers → Request Payload确认Content-Type: multipart/form-data; boundary----WebKitFormBoundary...Response应返回{state:SUCCESS,url:/upload/image/20240521/abc123.jpg,...}SSH登录服务器检查文件ls -lh /var/www/upload/image/20240521/ # 输出-rw-r--r-- 1 tomcat tomcat 4.8M May 21 14:22 abc123.jpg4.4 生产环境加固三道防线保障图片服务稳定防线1磁盘空间监控脚本/opt/scripts/check-upload-disk.sh#!/bin/bash # 检查 /var/www/upload 分区使用率 USAGE$(df /var/www/upload | awk NR2 {print $5} | sed s/%//) if [ $USAGE -gt 85 ]; then echo $(date): /var/www/upload usage is ${USAGE}% | mail -s Ueditor Disk Alert adminexample.com # 自动清理30天前的图片 find /var/www/upload/image/ -type f -mtime 30 -delete fi添加到crontab每小时执行0 * * * * /opt/scripts/check-upload-disk.sh防线2Nginx访问日志分析统计异常上传# 在Nginx配置中添加日志格式 log_format ueditor_upload $remote_addr - $remote_user [$time_local] $request $status $body_bytes_sent $http_referer $http_user_agent rt$request_time uct$upstream_connect_time uht$upstream_header_time urt$upstream_response_time; access_log /var/log/nginx/ueditor-upload.log ueditor_upload;用awk分析高频错误# 统计413错误请求体过大 awk $9 413 {count} END {print 413 errors:, count} /var/log/nginx/ueditor-upload.log防线3Ueditor前端二次校验防绕过在ueditor.config.js中添加// 前端文件大小校验与后端imageMaxSize一致 UE.registerUI(custom-upload, function(editor, uiName) { editor.addListener(beforeInsertImage, function(e, data) { for (var i 0; i data.length; i) { if (data[i].src data[i].src.size 10485760) { alert(图片大小不能超过10MB请压缩后重试); e.preventDefault(); return false; } } }); });5. 常见问题与排查技巧实录那些让你凌晨三点还在看日志的坑5.1 问题速查表按现象分类的解决方案现象可能原因排查命令/步骤解决方案上传按钮无响应控制台无任何日志Ueditor JS未加载成功curl -I http://domain.com/ueditor/ueditor.config.js检查HTTP状态码检查web.xml中servlet-mapping路径是否与HTML中script src路径一致确认ueditor/目录在webapps/ROOT/下上传后提示“网络错误”Network中controller.jsp返回空白controller.jsp中out.write()被缓冲未刷新在controller.jsp末尾添加out.flush()在out.write(enter.exec());后增加out.flush();强制输出缓冲区图片上传成功但Ueditor预览显示红叉URL为/upload/image/xxx.jpgNginx未配置/upload/反向代理curl -I http://domain.com/upload/image/xxx.jpg检查Nginx配置中location /upload/块是否生效alias路径是否指向正确目录上传大文件2MB时Tomcat报java.lang.OutOfMemoryError: Java heap spaceTomcat JVM内存不足ps aux | grep tomcat查看-Xmx参数编辑/opt/tomcat/bin/catalina.sh增加JAVA_OPTS-Xms1024m -Xmx2048m多用户同时上传部分图片文件名重复导致覆盖SimpleDateFormat非线程安全在controller.jsp中打印new Date().getTime()改用Instant.now().toString().replace([^a-zA-Z0-9], )生成时间戳或使用ThreadLocalSimpleDateFormat5.2 深度排查案例一次诡异的FileNotFoundException现象教师上传图片后Ueditor返回{state:SUCCESS,url:/upload/image/20240521/abc123.jpg}但访问该URL返回404。SSH检查文件存在Nginx日志显示200却无法加载。排查过程ls -l /var/www/upload/image/20240521/abc123.jpg→ 权限为-rw-------属主rootps aux \| grep nginx→ Nginx主进程由root运行worker进程由nginx用户运行sudo -u nginx ls /var/www/upload/image/20240521/→Permission denied。根本原因Ueditor的BinaryUploader在savePath不存在时调用new File(saveDir).mkdirs()创建目录但该方法创建的目录权限为700仅属主可读写而Nginx worker进程以nginx用户运行无权读取。解决方案方案A推荐在controller.jsp中创建目录后显式设置权限if (!dir.exists()) { dir.mkdirs(); // 设置目录权限为755 Runtime.getRuntime().exec(chmod 755 dir.getAbsolutePath()); }方案B更优修改Linux umask使新创建目录默认为755# 编辑 /etc/init.d/tomcat添加 umask 0225.3 实操心得来自17次失败的经验总结心得1永远不要信任Ueditor的config.json示例值官网示例中imagePathFormat写成/upload/{yyyy}{mm}{dd}/{filename}但{filename}会被Ueditor原样使用若教师上传病毒.jsp则直接生成/upload/20240521/病毒.jsp。我们必须在controller.jsp中强制重命名这是最后一道防线。心得2application.getRealPath(/)在Linux容器中可能返回null若Tomcat以-Dcatalina.base/opt/tomcat启动且webapps目录是软链接getRealPath会失效。此时应改用ServletContext.getResourceAsStream(/WEB-INF/jsp/config.json)读取配置

相关新闻

变压器油色谱DGA数据建模实战:从数据清洗到故障诊断模型落地

变压器油色谱DGA数据建模实战:从数据清洗到故障诊断模型落地

简介:这份变压器溶解气体数据集面向电力设备运维人员、故障诊断研究者及机器学习工程师,用于支撑变压器故障识别与智能监控模型的开发验证。数据覆盖中低温过热、高温过热、低能放电、高能放电、局部放电与正常状态六类典型工况,并围绕氢气、…

2026/10/13 15:28:08 阅读更多 →
CFSFDP参考代码实战:密度峰值聚类原理、决策图与调参指南

CFSFDP参考代码实战:密度峰值聚类原理、决策图与调参指南

简介:资源内含CFSFDP算法的Python参考代码,面向数据挖掘与聚类分析方向的学习者与研究人员,适用于包含离散、连续及模糊属性的复杂数据集,主要解决模糊数据分区前的特征选择与降维问题。压缩包共7个文件,约12KB&#x…

2026/10/13 15:27:06 阅读更多 →
【血糖】患者血糖数据、PID反馈控制和食物的切换,模拟混合人工胰腺Matlab实现

【血糖】患者血糖数据、PID反馈控制和食物的切换,模拟混合人工胰腺Matlab实现

✅作者简介:热爱科研的Matlab仿真开发者,擅长数学建模、模型改进、算法创新、程序设计科研仿真。🍎 往期回顾关注个人主页:完整代码获取 定制创新 论文复现私信🍊个人信条:做科研,博学之、审问之…

2026/10/13 15:27:06 阅读更多 →

最新新闻

MySQL 4.0 实战指南:老版本环境搭建、避坑与迁移要点

MySQL 4.0 实战指南:老版本环境搭建、避坑与迁移要点

简介:《MySQL 4.0.pdf》是一份聚焦MySQL 4.0版本的开源关系型数据库参考资料,适合数据库管理员、后端开发人员及系统学习数据库原理的读者,用于理解经典版本的功能体系与运维思路。资源为单个PDF文件,压缩包仅334KB,内…

2026/10/13 16:17:31 阅读更多 →
C++刷题指南:双指针反转、区间反转与原地扩容的字符串三题精讲

C++刷题指南:双指针反转、区间反转与原地扩容的字符串三题精讲

最近又刷了一轮力扣字符串题,把 344、541 和一道很经典的“替换数字”放到一起过了三遍。这三题单独看都不难,C 解法也都不超过 30 行,但把它们串在一起刷,你会发现它们正好覆盖了字符串操作中最容易被忽略的三类基本功&#xff1…

2026/10/13 16:17:31 阅读更多 →
Linux VFS核心架构深度拆解:从root device报错到数据落盘

Linux VFS核心架构深度拆解:从root device报错到数据落盘

前阵子帮朋友调一块嵌入式板子,内核起来后卡在了一行红字上:VFS: Cannot open root device "ram" or unknown-block(1,0): error -6折腾了一下午,最后发现只是内核配置没开 ramdisk 驱动,外加启动参数里 root 写得太想当…

2026/10/13 16:17:31 阅读更多 →
Cocos Creator房卡麻将源码拆解:状态机、洗牌算法与胡牌判断实战

Cocos Creator房卡麻将源码拆解:状态机、洗牌算法与胡牌判断实战

简介:一套基于CocosCreator引擎开发的房卡麻将棋牌游戏完整源码,面向Cocos Creator游戏开发者与棋牌类项目学习者。该项目围绕房卡模式展开,覆盖玩家创建私密房间、邀请好友开局、房卡购买与发放等核心链路,可作为从零搭建棋牌游戏…

2026/10/13 16:17:30 阅读更多 →
Linux VFS虚拟文件系统:从启动报错到架构与实战剖析

Linux VFS虚拟文件系统:从启动报错到架构与实战剖析

1. VFS到底是什么:从一次启动报错说起如果你玩过Linux内核开发或者自己折腾过嵌入式系统,大概率遇到过这么一行让人血压升高的日志:VFS: Cannot open root device "ram" or unknown-block(1,0): error -6 Please append a correct …

2026/10/13 16:17:30 阅读更多 →
基于SpringBoot+Vue的西安旅游系统管理平台设计与实现

基于SpringBoot+Vue的西安旅游系统管理平台设计与实现

每年到这个时间点,总有不少同学私信问我同一个问题:毕设选题到底选什么,既能赶紧把东西做出来,又不至于在答辩的时候被问得哑口无言。如果你正在发愁这件事,那这套基于SpringBootVue的西安旅游系统管理平台&#xff0c…

2026/10/13 16:16:29 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →