【Java】图片上传逻辑
后台逻辑:
后台服务,用Dubbo框架作为一个文件微服务
package cn.ymcd.aisw.service; import cn.ymcd.aisw.dto.RpcResult; /**
* @Description 文件服务
* @Author jianglun
* @Date 2022/4/7 0007 09:56
* @Version 1.0
*/
public interface IEFileService { /**
* 文件上传
*
* @param subDir 文件存储子目录,如果文件直接存储到对应业务目录的根目录下面,则直接传null即可
* @param fileName 文件名
* @param fileContent 文件内容
* @Author: jianglun
* @Date: 2022/04/07 10:01
**/
RpcResult uploadFile(String subDir, String fileName, byte[] fileContent); /**
* @param fileName 文件名,包含子目录
* @Description:删除文件
* @Author: jianglun
* @Date: 2022/04/07 10:03
**/
RpcResult deleteFile(String fileName);
}
Dubbo 文件接口实现:
package cn.ymcd.aisw.service.impl; import cn.ymcd.aisw.common.config.FileSettingConfig;
import cn.ymcd.aisw.dto.RpcFileDTO;
import cn.ymcd.aisw.dto.RpcResult;
import cn.ymcd.aisw.service.IEFileService;
import cn.ymcd.wss.util.log.YmcdLogger;
import com.alibaba.dubbo.config.annotation.Service; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import java.io.*; /**
* @Description 暴露文件dubbo服务
* @Author jianglun
* @Date 2022/4/7 10:07
* @Version 1.0
*/
@Service(interfaceClass = IEFileService.class, version = "1.0.0",timeout = 5000)
public class RpcFileServiceImpl implements IEFileService { private static final YmcdLogger LOGGER = new YmcdLogger(RpcFileServiceImpl.class); @Autowired
private FileSettingConfig config; @Override
public RpcResult uploadFile(String subDir, String fileName, byte[] fileContent) {
/* try {
String url = FileUtils.uploadFile(multiFile, fileStore + dir);
RpcFileDTO result = new RpcFileDTO();
result.setName(multiFile.getOriginalFilename());
result.setUrl(url);
return result;
} catch (IOException e) {
e.printStackTrace();
}
return null;*/ RpcResult result = RpcResult.getInstances(); // 1、参数验证
if (StringUtils.isBlank(fileName)) {
return result.error(RpcResult.NULL_ERR, "文件名为空!");
}
if (ArrayUtils.isEmpty(fileContent)) {
return result.error(RpcResult.NULL_ERR, "文件内容为空!");
} // 3、拼接文件存储目录,/appserver/[bustype]/[subDir]/xxxxx.jpg
StringBuilder filePath = new StringBuilder();
filePath.append(config.getBasePath().getDir_store_url()).append(File.separator);
if (StringUtils.isNotBlank(subDir)) {
filePath.append(subDir).append(File.separator);
} // 4、创建文件夹
try {
org.apache.commons.io.FileUtils.forceMkdir(new File(filePath.toString()));
} catch (IOException e) {
LOGGER.error("aisw-files-server 创建文件夹失败!", e);
return result.error(RpcResult.CREATE_DIR_ERR, "创建文件夹失败");
} // 5、保存文件
FileOutputStream fos = null;
InputStream in = null;
long ret = 0;
try {
filePath.append(fileName);
File saveFile = new File(filePath.toString());
fos = new FileOutputStream(saveFile);
in = new ByteArrayInputStream(fileContent);
ret = IOUtils.copyLarge(in, fos);
} catch (IOException e) {
LOGGER.error("aisw-files-server 存储文件失败!", e);
return result.error(RpcResult.STORAGE_FILE_ERR, "存储文件失败");
} finally {
IOUtils.closeQuietly(in);
IOUtils.closeQuietly(fos);
}
String storeUrl = filePath.toString().substring(config.getBasePath().getDir_store_url().length() + 1);
String url = config.getBasePath().getDir_agent_url() + File.separator + storeUrl;
RpcFileDTO rpcFileDTO = RpcFileDTO.newBuilder().agentUrl(url).storeUrl(storeUrl).build();
return (ret > 0) ? result.success().data(rpcFileDTO) : result.error(RpcResult.STORAGE_FILE_ERR, "拷贝存储文件失败");
} @Override
public RpcResult deleteFile(String fileName) {
RpcResult result = RpcResult.getInstances(); // 1、参数验证
if (org.apache.commons.lang3.StringUtils.isBlank(fileName)) {
return result.error(RpcResult.NULL_ERR, "文件名为空!");
} // 2、拼接文件存储目录,/appserver/[bustype]/subDir/xxxxx.jpg StringBuilder filePath = new StringBuilder();
filePath.append(config.getBasePath().getDir_store_url()).append(File.separator).toString();
filePath.append(fileName); // 3、删除文件
File file = new File(filePath.toString()); boolean deleteRet = file.delete();
return deleteRet ? result.success() : result.error(RpcResult.DELETE_FILE_ERR, "删除文件失败");
}
}
文件服务是作为服务提供者,没有消费的需要,所以只有发布的Dubbo接口
我的是后台管理服务,通过Dubbo接口标明一致,就可以调用文件服务了
这里是专门套接文件Dubbo接口写的文件上传删除接口
package cn.ymcd.aisw.common.controller; import cn.ymcd.aisw.common.CommonExtendUtils;
import cn.ymcd.aisw.common.RpcResult;
import cn.ymcd.aisw.service.IEFileService;
import cn.ymcd.comm.base.message.ResultMessage;
import cn.ymcd.comm.base.util.Assert;
import cn.ymcd.comm.security.annotation.IgnoreLoginCheck;
import cn.ymcd.wss.util.CommonUtil;
import cn.ymcd.wss.util.StringUtils;
import cn.ymcd.wss.util.log.YmcdLogger;
import cn.ymcd.wss.util.security.Base64Util;
import com.alibaba.dubbo.config.annotation.Reference;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile; import java.util.Map; /**
* @projectName: aisw-root
* @author: DaiZhiZhou
* @date: 2022年04月27日 10:04
* @version: 1.0
*/
@RestController
@RequestMapping("${sys.path}/file")
public class FileController { private static final YmcdLogger LOGGER = new YmcdLogger(FileController.class); @Reference(interfaceClass = IEFileService.class, version = "1.0.0", check = false)
private IEFileService fileService; /**
* 文件上传接口,上传的同时支持附带源文件地址可以删除源文件
* http://localhost:8083/sys/file/uploadFile
* @param file
* @param subDir
* @param originPath
* @return cn.ymcd.aisw.common.RpcResult
* @author DaiZhiZhou
* @createTime 2022/6/11 19:59
*/
@PostMapping(value = "/uploadFile")
@ApiOperation(value = "上传附件", notes = "上传附件")
@IgnoreLoginCheck
public RpcResult uploadFile(
@RequestParam(value = "file", required = true)
MultipartFile file,
@RequestParam(value = "subDir", required = true)
String subDir,
@RequestParam(value = "originPath", required = false)
String originPath
) {
Assert.isNull(file, ResultMessage.NULL_ERROR, "文件内容");
Assert.isEmpty(subDir, ResultMessage.NULL_ERROR, "文件存储子目录");
try {
String fileData = CommonUtil.encodeBase64File(file);
String storeFilename = CommonExtendUtils.packageRpcFileName(file);
LOGGER.info(fileService.toString()); // 不关心你是否删除成功
if (!StringUtils.isEmpty(originPath)) {
RpcResult rpcResult = fileService.deleteFile(originPath);
LOGGER.info("删除结果: {}", rpcResult.toString());
} return fileService.uploadFile(subDir, storeFilename, Base64Util.decode(fileData));
} catch (Exception ex) {
LOGGER.error("aisw-wx-applet 上传附件出错.错误信息:{}", ex.getMessage());
}
return null;
} /**
* http://localhost:8083/sys/file/deleteFile
* 删除附件
* @param param
* @return cn.ymcd.aisw.common.RpcResult
* @author DaiZhiZhou
* @createTime 2022/4/12 09:23
*
*/
@PostMapping(value = "/deleteFile")
@ApiOperation(value = "删除附件", notes = "删除附件")
@IgnoreLoginCheck
public RpcResult deleteFile(
@RequestBody
@ApiParam(name = "附件路径", value = "json格式", required = true)
Map<String, String> param
) {
String path = param.get("path");
Assert.isEmpty(path, ResultMessage.NULL_ERROR, "文件路径");
RpcResult rpcResult = fileService.deleteFile(path);
return rpcResult;
}
}
上传成功之后返回前缀路径 + 随机生成的文件名,将这个文件存储信息和其他记录一并放到MYSQL存储
如要删除文件,则把这个存储信息投送给删除接口即可
项目接口框架做了一个Xss过滤处理,文件接口是需要被Xss进行忽略的
另外后台服务还需要配置文件大小控制
xss.exclude.urls=/sys/file/uploadFile
spring.servlet.multipart.maxFileSize=10MB
spring.servlet.multipart.maxRequestSize=10MB
前端逻辑
重点是前端怎么控制图片资源不会造成文件余留
这是Vue表单项,一个酒店图标上传
<el-form-item label="酒店图标" prop="merchantIconUrl" :label-width="formLabelWidth">
<el-upload
class="avatar-uploader"
:action="uploadApi"
name="file"
:drag="true"
:data="apiParam"
:show-file-list="false"
:on-success="handleMerchantIconSuccess"
:before-upload="beforeMerchantIconUrl"
>
<img v-if="renderUrl" :src="renderUrl" class="avatar" :onerror="errImg">
<i v-else class="el-icon-plus avatar-uploader-icon" />
</el-upload>
</el-form-item>
初始的接口地址和参数:
uploadApi: `${window._config.default_service}sys/file/uploadFile`,
apiParam: { // 门店二维码路径参数
subDir: 'merchantPic'
},
源文件信息,和新的文件信息被区分开来
1、新文件使用一个暂存变量,每次上传的时候,都会存放在这个变量中
2、如果用户第一次上传觉得图片不行,会进行重复上传,那就需要把上一次上传的图片从磁盘中删除
3、如果用户撤销了操作,退出窗口,那么上传过的图片也需要删除掉
我的逻辑是这样写的:
handleMerchantIconSuccess(res, file) {
// 渲染图片
this.renderUrl = URL.createObjectURL(file.raw)
// 再次上传时删除上一张图片,所以这里需要存储这个图片的存储path
this.apiParam.originPath = res.data.data.storeUrl
// 上传成功之后返回的信息覆盖存储到暂存变量中
this.merchantIconDataCache.agentUrl = res.data.data.agentUrl
this.merchantIconDataCache.storeUrl = res.data.data.storeUrl
},
每一次上传就会携带上一次上传的存储path,这样就会删掉上次上传的图片
原来接口是不这么写的,上传就是上传,删除就是删除
那逻辑也是一样的,再写一个删除的调用,只不过是放在前端这里来写
handleMerchantIconSuccess(res, file) {
// 渲染图片
this.renderUrl = URL.createObjectURL(file.raw)
// 再次上传时删除上一张图片
this.deleteImageAction(this.merchantIconDataCache.storeUrl)
// 存储文件服务响应的地址,先存到缓存对象中
this.merchantIconDataCache.agentUrl = res.data.data.agentUrl
this.merchantIconDataCache.storeUrl = res.data.data.storeUrl
},
再最后表单提交时,去检查暂存变量是否存在新文件信息
如果存在,则表明需要把原记录的图片删除,新文件的图片需要更新上去
否则这个图片path不更新
/* 提交信息 */
submitForm(refTag) {
/* 如果暂存区存在,表示上传了新的图片,如果没有则删除这个属性,不进行更新 */
if (this.merchantIconDataCache.storeUrl) {
// 覆盖新图之前还要删除原来的图片
this.deleteImageAction(this.form.merchantIconUrl)
this.form.merchantIconUrl = this.merchantIconDataCache.storeUrl
} // MAC地址正则 https://blog.csdn.net/superbeyone/article/details/83748369
const regexForMac = /^([0-9a-fA-F]{2})(([/\s:][0-9a-fA-F]{2}){5})$/
// 对MAC地址参数处理 https://blog.csdn.net/weixin_40195422/article/details/109520572
if (this.form.mac && this.form.mac.length === 6 * 2) {
const convertMac = []
for (let i = 0; i < this.form.mac.length; i += 2) {
convertMac.push(this.form.mac.slice(i, i + 2))
}
console.log(this.form.mac, convertMac)
this.form.mac = convertMac.join('-')
} else if (this.form.mac && this.form.mac.match(regexForMac)) {
this.form.mac = this.form.mac.replaceAll(':', '-')
} updatePlatformInfo(this.form).then(res => {
if (res.data === true) {
this.$message({
message: '更新成功',
type: 'success'
})
this.clearLegacy()
this.$emit('close', null)
}
})
},
clearLegacy() {
// 重置暂存地址,防止回调删除图片
this.merchantIconDataCache.storeUrl = ''
this.merchantIconDataCache.agentUrl = ''
}
用户撤销操作的时候检查暂存变量,如果存在path信息,则调用删除
// 撤销事件绑定的方法
cancel() {
// 调用父组件的close事件
this.$emit('close', this.merchantIconDataCache.storeUrl)
},
// 这里写的有点绕,是让父组件调用这个方法删除
deleteImageAction(filePath) {
if (filePath) {
deleteFile({ path: filePath }).then(res => {
console.log(`then -> ${JSON.stringify(res)}`)
})
}
}, // 父组件的close方法
merchantDialogClose(val) {
console.log(val)
this.merchantDialogVisible = false
this.$refs.merchantSetting.deleteImageAction(val)
},

【Java】图片上传逻辑的更多相关文章
- java图片上传(mvc)
最近有开始学起了java,好久没写文章了,好久没来博客园了.最近看了看博客园上次写的图片上传有很多人看,今天在一些篇关于java图片上传的.后台接收用的是mvc.不墨迹了,直接上图. 先看目录结构.i ...
- js 利用iframe和location.hash跨域解决的方法,java图片上传回调JS函数跨域
奶奶的:折腾了我二天,最终攻克了!网上有非常多样例. 但跟我的都不太一样,费话不多说了,上图 上代码: IE ,firefix,chrome 測试通过 js :这个主页面,部分代码, functi ...
- java 图片上传
代码是最有力量的,嘎嘎 @CrossOrigin@ApiOperation(value = "上传图片", notes = "上传图片", httpMethod ...
- java图片上传,通过MultipartFile方式,如果后台获取null检查是否缺少步骤
本方法基于springMvc 1.首先需要在webap下创建images 2.在springmvc.xml上引入 <bean id="multipartResolver" c ...
- Java图片上传压缩处理
所需要的jar包在:\jdk1.7.0_25\jre\lib\rt.jar里面 package util; import java.awt.Image; import java.awt.image.B ...
- java图片上传及图片回显1
目的:选择图片,进行图片回显之后将图片保存到服务器上(PS:没有使用任何插件,样式很丑) 实现方式: js+servlet+jsp的方式来实现 事先准备: 文件上传处理在浏览器中是以流的形式提交到服务 ...
- JAVAEE——SpringMVC第二天:高级参数绑定、@RequestMapping、方法返回值、异常处理、图片上传、Json交互、实现RESTful、拦截器
1. 课前回顾 https://www.cnblogs.com/xieyupeng/p/9093661.html 2. 课程计划 1.高级参数绑定 a) 数组类型的参数绑定 b) List类型的绑定 ...
- UEditor之实现配置简单的图片上传示例
UEditor之实现配置简单的图片上传示例 原创 2016年06月11日 18:27:31 开心一笑 下班后,阿华到楼下小超市买毛巾,刚买完出来,就遇到同一办公楼里另一家公司的阿菲,之前与她远远的有过 ...
- 5行代码实现微信小程序图片上传与腾讯免费5G存储空间的使用
本文介绍了如何在微信小程序开发中使用腾讯官方提供的云开发功能快速实现图片的上传与存储,以及介绍云开发的 5G 存储空间的基本使用方法,这将大大提高微信小程序的开发效率,同时也是微信小程序系列教程的视频 ...
- 快速高效实现微信小程序图片上传与腾讯免费5G存储空间的使用
本文介绍了如何在微信小程序开发中使用腾讯官方提供的云开发功能快速实现图片的上传与存储,以及介绍云开发的 5G 存储空间的基本使用方法,这将大大提高微信小程序的开发效率 对于一般的图片上传功能开发,我们 ...
随机推荐
- k8s——集群环境问题合集
创建集群 k8s集群创建 集群环境问题合集 重置master节点 kubeadm reset -f # -f 强制重置 可选 重置node节点 # 当你的master节点重置后,node节点需要重新加 ...
- 安装图形化界面时候报错 Transaction check error: file /boot/efi/EFI/centos from install of fwupdate-efi-12-5.el7.centos.x86_64 conflicts with file from package grub2-common-1:2.02-0.65.el7.centos.2.noarch
报错 Transaction check error:file /boot/efi/EFI/centos from install of fwupdate-efi-12-5.el7.centos.x8 ...
- 关于正在开发中的DjangoStarter v3版本
前言 最近做的这个项目大量使用了 python 及其相关的生态,因此自然而然选择了我的 DjangoStarter 作为后端框架 之前 v2 版本是用 RestFramework 做接口的,后面我试用 ...
- form表单提交后,页面弹出成功或者失败的信息
Ssm 中用RedirectAttributes做提示消息` @RequiresPermissions("hic:zybl:hicZybl:edit") @RequestMappi ...
- AI赋能ITSM:企业运维跃迁之路
随着企业信息化建设的深入,IT运维管理作为保证企业信息系统稳定运行的重要工作,越来越受到重视. 那么,什么是IT运维呢? 简单地说,IT运维是一系列维护.管理和优化企业IT基础设施.系统和应用程序的活 ...
- 【iOS】Class对构造简洁代码很有帮助
(这到底取的是什么标题啊) 首先先看这段代码(有删减) @property (nonatomic, copy)NSMutableArray <NSMutableArray *>*datas ...
- Canvas绘制圆角图片
效果图: 思路: 先绘制一个圆角长方形 在画布中裁剪下来 在圆角长方形内绘制图片 图片四个角超出圆角长方形的区域被隐藏 具体代码: <!DOCTYPE html> <html lan ...
- CF1591F 题解
先不管值域,设计状态 \(dp_{i,j}\) 表示考虑前 \(i\) 个数最后一个数为 \(j\) 的方案数,那么有如下转移: \[dp_{i,j} = dp_{i-1,k} (j \not = k ...
- 痞子衡嵌入式:浅聊恩智浦i.MXRT官方SDK里关于串行Flash相关的驱动与例程资源(上篇)
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是恩智浦i.MXRT官方SDK里关于串行Flash相关的驱动与例程资源. 经常有同事以及 i.MXRT 客户咨询痞子衡,咱们恩智浦官方 S ...
- Linux启动Java程序jar包Shell脚本
手动方式启动和终止java程序 启动java程序jar:nohup java -jar XXX.jar 查看程序占用pid:ps -ef | grep XXX.jar 或 jps jps是jdk提供的 ...