图片上传(ftp+vue+springboot+ruoyi)

图片上传(ftp+vue+springboot+ruoyi)

1. 问题分析:

  1. 前端通过el-upload实现图片上传
  2. 后端获取到MultipartFile类型的数据,将图片上传到ftp服务器上,然后返回上传后的相对路径,并返回给前端
  3. 前端拿到返回后的相对路径(url),存入form表单,一起传给后端,保存到数据库
  4. 前端通过请求得到的url,拼凑http字符串赋给src进行显示

2. 后端上传工具类:

  1. 文件上传到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;
    }
}
  1. 连接配置类
    • 其中编写静态方法,作为分类目录(用于上传时目录的生成)

image-20210611192811548

  1. 配置ftp服务器信息

image-20210611193140310

  1. 工具类分析

image-20210611193521765

  • 因为工具类中的方法为静态方法,所以只能只用静态成员变量。而工具类方法要使用FtpConfig中的配置信息,因此也必须声明为static的。但是声明为static之后,无法通过@Autowire来注入FtpConfig对象,因为@Autowire在对象实例化的时候注入。而使用静态方法又不需要实例化对象,直接通过FtpUtils.upload()的方式调用方法。如果用@Autowire的话就会产生空指针异常null
  • 解决方法就类似下图,使用以下五步

image-20210611194315587

因为执行顺序如下:Constructor(构造方法@Component) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)

upload方法分析:

image-20210611194935590

  • 除蓝框的内容基本都是操作现成的API,蓝框部分是在ftp服务器(ftp服务器已经配置好,拥有操作文件夹的权限)上创建文件夹。
  • 先将路径分割成数组,在for循环中,先切换工作路径changeWorkingDirectory,如果切换不成功,说明文件夹不存在,则创建文件夹(一次只能创建一级),然后切换工作路径。循环进行,直到最后一层文件夹
  • 调用ftp的api进行上传,需要传入输入流

download方法分析

image-20210611195953205

  • 除蓝框的内容基本都是操作现成的API
  • web服务端可以在构造File对象后,直接判断路径是否存在,不存在的话直接创建。
  • 切换ftp的工作路径,然后在ftp服务器上判断下载的文件是否存在
  • 存在的话,用web服务器上的路径构造的File对象创建输出流对象进行下载(调用ftp的api)

3. 后端图像上传和显示接口

上传接口:

image-20210611201350270

构造上传到ftp服务器上的相对路径,调用工具类直接上传,数据接口参数为MultipartFile类型,要求request header中的content-type 为multipart/form-data类型

image-20210611201830805

显示接口:

  • 可以共用一个显示接口

image-20210611202119589

  • 通过传入的url,找到图片,创建输入流,然后写入HttpServletResponse的输出流,返回图片数据。
  • 用image标签的src属性请求此接口

image-20210611203955938

因为框架使用了jwt,需要认证,故放行common接口

image-20210611204245921

4. 前端图像上传和显示

上传
  1. el-form-item
 <el-form-item label="宠物照片" prop="image">
          <el-upload
            action=""
            list-type="picture-card" 
            
            accept="image/*" 
            :limit=1 
            :file-list="imagelist"
            :on-preview="handlePictureCardPreview" 
            :on-remove="handleRemove" 
            :before-upload="beforeAvatarUpload" 
            :on-error="imgUploadError"
            :on-change="selectImageChange" 
            :http-request="upload"
            class="avatar-uploader"
            :class="{ disabled: uploadDisabled }"
            :auto-upload="false"
          >
            <i class="el-icon-plus"></i>
          </el-upload>
          <el-dialog :visible.sync="dialogVisible">
            <img width="100%" :src="dialogImageUrl" alt="" />
          </el-dialog>
        </el-form-item>

分析(详细属性代表的含义可以查看element UI官网)

image-20210611205035197

图片上传的时候,上传一个图像之后,又会出现一个上传框,通过以下方式进行解决

image-20210611205643658

  • 添加计算属性,当图片个数大于0的时候,disabled:true

image-20210611205755400

  • 添加disabled的样式

image-20210611205925286

一些钩子函数

image-20210611210217734

标号1是构造图像的上传对象,fd就是要传入后端的那个MultipartFile,参数file是使用el-upload标签后的默认参数,包含上传文件的信息file.raw

image-20210611210633703

addImgData函数请求后端接口

image-20210611210709026

显示:

el-table-column

image-20210611210931892

  • 使用src
  • 点击图片会激活previewImg函数,此函数中将el-dialog显示设为true,并且给imgSrc赋值

image-20210611211223280

点击修改的话会弹出dialog,因为要将dialog上传框中的数据进行回显

image-20210611211436954

在handleUpdate函数中为el-upload标签中的file-list的属性值imagelist数组填充图片url信息用于回显

image-20210611211521638

  • 6
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值