- 前端通过el-upload实现图片上传
- 后端获取到MultipartFile类型的数据,将图片上传到ftp服务器上,然后返回上传后的相对路径,并返回给前端
- 前端拿到返回后的相对路径(url),存入form表单,一起传给后端,保存到数据库
- 前端通过请求得到的url,拼凑http字符串赋给src进行显示
- 文件上传到ftp服务器的工具类FtpUtils
- upload上传文件到ftp服务器
- download从ftp服务器下载文件到web服务器
public class FtpUtils {
/**
* 默认大小 50M
*/
public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024;
/** 此处静态方法不能用注入的方式初始化 否则为null 因为@Atuowire在初始化此对象之后执行 而调用静态方法不会初始化对象*/
static FtpConfig ftpConfig;
@Autowired
FtpConfig ftp;
@PostConstruct
public void init(){
ftpConfig=this.ftp;
}
/**
* @param baseDir 上传的路径 为相对路径
* @param file 要上传到ftp服务器的文件
*/
public static String upLoad(String baseDir, MultipartFile file) throws Exception {
FTPClient ftp = new FTPClient();
try {
/** 1. 检查文件大小和扩展名是否符合要求*/
assertFile(file);
/** 2. 产生新的文件名,目的使得文件名统一为英文字符加数字;fileName包含文件后缀名*/
String fileName=reBuildFileName(file);
/** 3. 连接ftp服务器*/
ftp.connect(ftpConfig.getIp(),ftpConfig.getPort());
ftp.login(ftpConfig.getUsername(),ftpConfig.getPassword());
if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
// 不合法时断开连接
ftp.disconnect();
throw new IOException("ftp连接异常,异常码为:"+ftp.getReplyCode());
}
String path=ftpConfig.getUploadPath()+baseDir;
String[] dirs=path.split("/");
ftp.changeWorkingDirectory("/");
/** 4. 判断ftp服务器目录是否存在 不存在则创建*/
for(int i=0; i<dirs.length && dirs != null;i++){
if(! ftp.changeWorkingDirectory(dirs[i])){
if(ftp.makeDirectory(dirs[i])){
if(! ftp.changeWorkingDirectory(dirs[i]))
throw new Exception("打开文件夹"+dirs[i]+"失败");
}else{
throw new Exception("创建文件夹"+dirs[i]+"失败");
}
}
}
/** 5.切换ftp文件操作目录*/
ftp.changeWorkingDirectory(path);
/** 6.上传文件*/
// 设置文件类型,二进制
ftp.setFileType(FTPClient.BINARY_FILE_TYPE);
// 设置缓冲区大小
ftp.setBufferSize(3072);
// 上传文件
ftp.storeFile(fileName, file.getInputStream());
// 登出服务器
ftp.logout();
return baseDir+"/"+fileName;
}catch (Exception e){
throw new Exception(e.getMessage(), e);
}finally {
/** 关闭*/
// 判断连接是否存在
if (ftp.isConnected()) {
// 断开连接
ftp.disconnect();
}
}
}
/**
* @description: 从ftp服务器上下载文件到本地
* @param baseDir ftp服务器文件路径 为相对路径 使用数据库中的url路径(ftp存在共享文件夹)
* @param fileName 文件名
* @param localDir web服务器本地存储路径 为相对路径 使用数据库中的url路径
* */
public static boolean downLoad(String localDir,String baseDir,String fileName){
boolean result = false;
String localPath=ftpConfig.getDownPath()+localDir;
String ftpPath=ftpConfig.getUploadPath()+baseDir;
FTPClient ftp = new FTPClient();
OutputStream os = null;
try {
// 连接至服务器,端口默认为21时,可直接通过URL连接
ftp.connect(ftpConfig.getIp(), ftpConfig.getPort());
// 登录服务器
ftp.login(ftpConfig.getUsername(), ftpConfig.getPassword());
// 判断返回码是否合法
if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
// 不合法时断开连接
ftp.disconnect();
// 结束程序
return result;
}
// 设置文件操作目录
ftp.changeWorkingDirectory(ftpPath);
// 设置文件类型,二进制
ftp.setFileType(FTPClient.BINARY_FILE_TYPE);
// 设置缓冲区大小
ftp.setBufferSize(3072);
// 设置字符编码
ftp.setControlEncoding("UTF-8");
// 构造本地文件夹
File localFilePath = new File(localPath);
if (!localFilePath.exists()) {
localFilePath.mkdirs();
}
// 构造本地文件对象
File localFile = new File(localPath + "/" + fileName);
// 获取文件操作目录下所有文件名称
String[] remoteNames = ftp.listNames();
// 循环比对文件名称,判断是否含有当前要下载的文件名
for (String remoteName : remoteNames) {
if (fileName.equals(remoteName)) {
result = true;
}
}
// 文件名称比对成功时,进入下载流程
if (result) {
// 构造文件输出流
os = new FileOutputStream(localFile);
// 下载文件 写入到输出流中
result = ftp.retrieveFile(fileName, os);
// 关闭输出流
os.close();
}
// 登出服务器
ftp.logout();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
// 判断输出流是否存在
if (null != os) {
// 关闭输出流
os.close();
}
// 判断连接是否存在
if (ftp.isConnected()) {
// 断开连接
ftp.disconnect();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
/**
* @desecription: 删除web服务器本地文件
* @param realFile: web服务器本地文件
* */
public static boolean delrealFile(String realFile){
File file =new File(realFile);
if( file.exists()&&file.isFile()){
if(file.delete()){
//System.out.println("删除成功");
return true;
}else {
System.out.println("删除失败");
return false;
}
}else {
System.out.println("删除"+realFile+"文件不存在或者不是一个文件类型");
return false;
}
}
/**
* @Description: 判断文件大小是否超过50M 以及判断文件扩展名是否是image类型
* */
public static void assertFile(MultipartFile file)throws FileSizeLimitExceededException,InvalidExtensionException{
long size=file.getSize();
if(size>DEFAULT_MAX_SIZE){
throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE/1024/1024);
}
String fileName=file.getOriginalFilename();
String extension=getExtension(file);
if(! isAllowedExtension(extension)){
throw new InvalidExtensionException.InvalidImageExtensionException(MimeTypeUtils.IMAGE_EXTENSION, extension,
fileName);
}
}
/**
* 编码文件名
*/
public static final String reBuildFileName(MultipartFile file)
{
String fileName = file.getOriginalFilename();
String extension = getExtension(file);
fileName = IdUtils.fastUUID() + "." + extension;
return fileName;
}
/**
* 获取文件名的后缀
*
* @param file 表单文件
* @return 后缀名
*/
public static final String getExtension(MultipartFile file)
{
String extension = FilenameUtils.getExtension(file.getOriginalFilename());
if (StringUtils.isEmpty(extension))
{
extension = MimeTypeUtils.getExtension(file.getContentType());
}
return extension;
}
/**
* 判断MIME类型是否是允许的Image类型
*
* @param extension
* @return
*/
public static final boolean isAllowedExtension(String extension)
{
for (String str : MimeTypeUtils.IMAGE_EXTENSION)
{
if (str.equalsIgnoreCase(extension))
{
return true;
}
}
return false;
}
}
- 连接配置类
- 其中编写静态方法,作为分类目录(用于上传时目录的生成)
- 配置ftp服务器信息
- 工具类分析
- 因为工具类中的方法为静态方法,所以只能只用静态成员变量。而工具类方法要使用FtpConfig中的配置信息,因此也必须声明为static的。但是声明为static之后,无法通过@Autowire来注入FtpConfig对象,因为@Autowire在对象实例化的时候注入。而使用静态方法又不需要实例化对象,直接通过FtpUtils.upload()的方式调用方法。如果用@Autowire的话就会产生空指针异常null
- 解决方法就类似下图,使用以下五步
因为执行顺序如下:Constructor(构造方法@Component) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)
upload方法分析:
- 除蓝框的内容基本都是操作现成的API,蓝框部分是在ftp服务器(ftp服务器已经配置好,拥有操作文件夹的权限)上创建文件夹。
- 先将路径分割成数组,在for循环中,先切换工作路径changeWorkingDirectory,如果切换不成功,说明文件夹不存在,则创建文件夹(一次只能创建一级),然后切换工作路径。循环进行,直到最后一层文件夹
- 调用ftp的api进行上传,需要传入输入流
download方法分析
- 除蓝框的内容基本都是操作现成的API
- web服务端可以在构造File对象后,直接判断路径是否存在,不存在的话直接创建。
- 切换ftp的工作路径,然后在ftp服务器上判断下载的文件是否存在
- 存在的话,用web服务器上的路径构造的File对象创建输出流对象进行下载(调用ftp的api)
上传接口:
构造上传到ftp服务器上的相对路径,调用工具类直接上传,数据接口参数为MultipartFile类型,要求request header中的content-type 为multipart/form-data类型
显示接口:
- 可以共用一个显示接口
- 通过传入的url,找到图片,创建输入流,然后写入HttpServletResponse的输出流,返回图片数据。
- 用image标签的src属性请求此接口
因为框架使用了jwt,需要认证,故放行common接口
- el-form-item
分析(详细属性代表的含义可以查看element UI官网)
图片上传的时候,上传一个图像之后,又会出现一个上传框,通过以下方式进行解决
- 添加计算属性,当图片个数大于0的时候,disabled:true
- 添加disabled的样式
一些钩子函数
标号1是构造图像的上传对象,fd就是要传入后端的那个MultipartFile,参数file是使用el-upload标签后的默认参数,包含上传文件的信息file.raw
addImgData函数请求后端接口
el-table-column
- 使用src
- 点击图片会激活previewImg函数,此函数中将el-dialog显示设为true,并且给imgSrc赋值
点击修改的话会弹出dialog,因为要将dialog上传框中的数据进行回显
在handleUpdate函数中为el-upload标签中的file-list的属性值imagelist数组填充图片url信息用于回显