【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 存储空间的基本使用方法,这将大大提高微信小程序的开发效率 对于一般的图片上传功能开发,我们 ...
随机推荐
- Qt-udp通信
1 简介 参考视频:https://www.bilibili.com/video/BV1XW411x7NU?p=61 说明:UDP是面向无连接的,客户端并不与服务器不建立连接,直接向服务器发送数据, ...
- Java异常中throw 与throws的区别
throw 与 throws区别 在Java中,throws和throw是两个不同的关键字,它们在异常处理中起着不同的作用. throws关键字: throws用于声明一个方法可能会抛出的异常.当一个 ...
- 【Socket】解决UDP丢包问题
一.介绍 UDP是一种不可靠的.无连接的.基于数据报的传输层协议.相比于TCP就比较简单,像写信一样,直接打包丢过去,就不用管了,而不用TCP这样的反复确认.所以UDP的优势就是速度快,开销小.但是随 ...
- ETL工具-nifi干货系列 第七讲 处理器JoltTransformJSON(续)
第六讲教程只简单介绍了Jolt的chain转换模式,本节课介绍下Jolt的各种转换模式. 点击的处理器JoltTransformJSON高级配置选项,进行测试Jolt的转换模式. 1.Cardinal ...
- linux系统下,jdk的安装和配置教程,以jdk-8u311为例
1.官方下载 下载地址:https://www.oracle.com/ 本文以jdk8为例, 1)下载地址:https://www.oracle.com/java/technologies/downl ...
- Redis数据类型有哪些?
a.String(字符串) b.Hash(hash表) c.List(链表) d.Set(集合) e.SortedSet(有序集合zset)
- 18.9k star!一个高性能的嵌入式分析型数据库,主要用于数据分析和数据处理任务。
大家好,今天给大家分享的是一个开源的面向列的关系数据库管理系统(RDBMS). DuckDB是一个嵌入式的分析型数据库,它提供了高性能的数据分析和数据处理能力.DuckDB的设计目标是为数据科学家.分 ...
- 在线RSA公钥私钥生成工具
在线RSA非对称加密公钥私钥生成工具,提供便捷.安全的公私钥生成服务.支持多种密钥长度选择,满足个性化需求.一键生成PEM格式证书,让您快速实现数据加密与身份验证,保障数据安全,提升网络安全防护能力. ...
- vba--分拆工作薄
Sub 分拆工作薄() '分拆工作薄到当前文件夹 Dim sht As Worksheet Dim MyBook As Workbook Application.DisplayAlerts = Fal ...
- 韦东山freeRTOS系列教程之【第五章】队列(queue)
目录 系列教程总目录 概述 5.1 队列的特性 5.1.1 常规操作 5.1.2 传输数据的两种方法 5.1.3 队列的阻塞访问 5.2 队列函数 5.2.1 创建 5.2.2 复位 5.2.3 删除 ...