【Java项目】Vue+ElementUI+Ceph实现多类型文件上传功能并实现文件预览功能

news2024/12/23 5:26:11

文章目录

  • 效果演示
  • 前端
  • 后端Java

效果演示

在这里插入图片描述
先说一下我们的需求,我们的需求就是文件上传,之前的接口是只支持上传图片的,之后需求是需要支持上传pdf,所以我就得换接口,把原先图片上传的接口换为后端ceph,但是其实大致的处理流程都差不多,都是上传到后端然后得到url地址。

要实现点击预览文件,那么就需要使用到element的groupPreview。
在这里插入图片描述

前端

ElementUI
文件上传的页面使用的是ElementUI的
在这里插入图片描述
下面是index.vue页面,有点小bug(但是我真改不动,前端还在学习中)



<template>
    <div>
      <el-upload
        class="upload-demo"
        :action="uploadAction"
        :on-remove="handleRemove"
        :on-success="handleSuccess"
        :on-error="handleError"
        :before-upload="beforeUpload"
        :on-preview="groupPreview"
        multiple
        :file-list="fileList"
        >
        <el-button size="small" type="primary">点击上传</el-button>
        <div slot="tip" class="el-upload__tip">只能上传jpg/png/pdf文件,且不超过20M</div>
        <!-- <div v-for="(file, index) in fileList" :key="index">
      <a :href="getURL(file)" target="_blank">{{ file.name }}</a>
    </div> -->
      </el-upload>
    </div>
</template>
<script setup name="uploadImage">
import { ref, defineProps, onMounted, defineEmits } from 'vue';
import { ElMessage } from 'element-plus';
const props = defineProps(['modelValue'])
const fileList = ref([])
const urls = ref([])
const uploadAction = ref('/merchant/api/common/upload/image')
const emits = defineEmits(['update:modelValue','input'])

onMounted(() => {
    setDefaultFileList()
})
const setDefaultFileList = () => {
    //这里的modelValue就是mainProduct
    if (props.modelValue && props.modelValue.length > 0) {
        fileList.value = []

        let i = 1;
        props.modelValue.forEach(element => {
            fileList.value.push({
            //   name:  "文件"+i,
                url: element,
              name: "点击预览文件", 
            })
            i=i+1;
        });
    }
}
const getURL = (file) => {
      // 根据文件索引获取对应的URL地址
      return this.urls[this.fileList.indexOf(file)];
    }
const handleRemove = (file, fileList) => {
    let fileList1 = fileList.map(item => item.response.data.name)
    emits("update:modelValue", fileList1);

}

//文件上传还有一点bug 
//如果不把所有文件都删掉,只删除部分的那么上传失败
const handleSuccess = (response, file, fileList) => {
    let fileList1 = fileList.map(item => item.response.data.name)
    emits("update:modelValue", fileList1);
}

const handleError = (error) => {
    console.log('handleError', error)
    ElMessage.error('文件上传失败');
}
const groupPreview = (file)=>{
    window.open(file.url);
}
const beforeUpload = (file) => {
    // console.log(file)
    const isLt2M = file.size / 1024 / 1024 < 20;
    if (!isLt2M) {
        ElMessage.error('上传文件大小不能超过 20MB!');
    }
    return isLt2M;
}
</script>

大概情况就是我们添加文件的时候,会发送一个请求到后端,这个后端的路径为

const uploadAction = ref('/merchant/api/common/upload/image')

当点击上传图片之后,我们的网页会发送这个请求,这个请求其实是会被router处理的,如下

var express = require("express");
var router = express.Router();
var request = require("superagent");
var multer = require("multer");

module.exports = (app) => {
  /**
   * 上传图片
   */
  router.post("/upload/image", async (req, res, next) => {
    var storage = multer.memoryStorage();
    var upload = multer({ storage }).single("file");
    upload(req, res, async (err) => {
      try {
        const url = "http://localhost:8081/supplier/outerapi/api/ceph/upload";
        const response = await request.post(url).attach("file", req.file.buffer, req.file.originalname);
        const result = JSON.parse(response.text);
        res.send({
          code: 200,
          msg: "图片上传成功",
          data: {
          //这里的name,result。data的值就是那个url地址
          //这里就是向后端接口发送请求然后获取url地址
            name: result.data,
          },
        });
      } catch (err) {
        res.send({
          code: 500,
          msg: "图片上传异常",
        });
      }
    });
  });
  //使用/merchant/api/common作为路径前缀
  app.use("/merchant/api/common", router);
};

可以看到这里有一个url,这个url就是你的后端处理前端文件上传的那个接口了。
并且可以看到我们的返回类型要求是code,msg,data,其中data要求有name和url,分别是文件名称和文件路径。

后端Java

我们先来看控制层代码,这里我们的OSS服务使用的是ceph,你也可以使用minio等来代替。

 /**
     * 上传订单附件接口,文件可以一次传多个
     * Content-Type:multipart/form-data;
     * 文件参数名:file
     *
     * @param files 请求的文件
     * @return 返回
     */
    @PostMapping("/upload")
    public BaseResponse<List<FileInfo>> uploadFile(@RequestParam("file")
                                                     List<MultipartFile> files) {
        BaseResponse<List<FileInfo>> res = new BaseResponse<List<FileInfo>>();
        res.setCode(500);
        res.setMsg("上传失败,请稍后重试");
        ArrayList<FileInfo> listFile = new ArrayList<FileInfo>();
        try {
            //遍历请求头里的文件
            //for (int i = 0; i < files.size(); i++) {
            for (MultipartFile file : files) {
                //获取文件的原始文件名
                String fileName = file.getResource().getFilename();
                //获取文件后缀,如 .jpg
                String fileSuffix = fileName.substring(fileName.lastIndexOf("."));
                //ceph请求参数
                CephRequest cephRequest = new CephRequest();
                cephRequest.originFileName = fileName;
                //文件名设置一个随机数,避免重复
                SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
                String uuid = UUID.randomUUID().toString();
                cephRequest.setFileName(dateFormat.format(new Date())
                        + uuid.substring(uuid.lastIndexOf("-")) + fileSuffix);
                //这里可以设置块名称也可以不设置,不设置用默认的
                cephRequest.setBlockName("");
                cephRequest.setMaxDays(5 * 365); //五年
                /* 设置请求头信息 */
                cephRequest.setContextType(file.getContentType());
                // 从OkHttp里面提交的postfileName 需要根据实际文件后缀修改contentType
                if (cephRequest.getContextType().equals("application/from-data")) {
                    if (fileName.endsWith(".pdf")) {
                        cephRequest.setContextType("application/pdf");
                    } else if (fileName.endsWith(".jpeg")) {
                        cephRequest.setContextType("image/jpeg");
                    } else if (fileName.endsWith(".png")) {
                        cephRequest.setContextType("image/png");
                    } else if (fileName.endsWith(".bmp")) {
                        cephRequest.setContextType("image/bmp");
                    } else if (fileName.endsWith(".jpg")) {
                        cephRequest.setContextType("image/jpg");
                    }
                }
                cephRequest.setFileStream(file.getInputStream());
                LogUtil.info(JSON.toJSONString(cephRequest), "cephRequest");
                //上传文件到ceph
                var cephResponse = cephService.uploadCeph(cephRequest);
                if (cephResponse.isOk()) {
                    //ceph上传成功后,添加到返回参数里
                    listFile.add(new FileInfo(cephRequest.getOriginFileName(),cephResponse.getFileUrl()));
                } else {
                    res.setMsg(cephResponse.getErrorMsg());
                    return res;
                }
            }
            //上传成功的标识
            if (listFile.size() > 0) {
                res.setCode(200);
                res.setMsg("ok");
            }
        } catch (Exception ex) {
            res.setCode(500);
            res.setMsg("上传失败,请稍后重试!" + ex.getMessage());
        } finally {
            res.setData(listFile);
        }
        return res;

实体类

@Data
public class CephRequest {
    /**
     * 要保存的文件名称
     */
    public String fileName;
    /**
     * 要上传的文件流
     */
    public InputStream fileStream;
    /**
     * 设置文件内容的类型
     */
    public String contextType;
    /**
     * 要保存的天数,不传默认是365*3
     */
    public int maxDays;
    /**
     * 块名称,不传用默认的
     */
    public String blockName;
    /**
     * 原始文件名.
     */
    public String originFileName;
}

然后是ceph的service层


@Service
public class CephService {
    /**
     * CEPH服务地址
     */
    public static String SERVICE_URL = "";

    /**
     * 块名称,可以自定义修改
     */
    public static String BLOCK_NAME = "";
    /**
     * ceph key
     */
    public static String ACCESS_KEY = "";
    /**
     * ceph密钥
     */
    public static String SECRET_KEY = "";
    /**
     * CEPH客户端
     */
    private AmazonS3Client s3client = null;
    /**
     * oss存储管理类
     */
    private final ICephManager cephManager;

    public CephService(ICephManager cephManager) {
        this.cephManager = cephManager;
    }

    public CephResponse uploadCeph(CephRequest param) {
        CephResponse res = new CephResponse();
        res.setOk(false);
        try {
            // 一、初始化ceph客户端
            AWSCredentials credentials = new BasicAWSCredentials(ACCESS_KEY, SECRET_KEY);
            ClientConfiguration clientCfg = new ClientConfiguration();
            clientCfg.setProtocol(Protocol.HTTP);
            s3client = new AmazonS3Client(credentials, clientCfg);
            //设置存储的服务器
            s3client.setEndpoint(SERVICE_URL);
            s3client.setS3ClientOptions(new S3ClientOptions().withPathStyleAccess(true));

            //二、上传文件
            InputStream input = param.getFileStream();
            //参数验证
            if ("".equals(param.getFileName())) {
                res.setErrorMsg("需要文件名参数");
                return res;
            } else if (input == null) {
                res.setErrorMsg("未获取到文件流");
                return res;
            } else if (param.getMaxDays() <= 0) {
                res.setErrorMsg("有效期必须大于0");
                return res;
            }
            String bucket = BLOCK_NAME;
            if (!"".equals(param.getBlockName())) {
                bucket = param.getBlockName();
            }
            // 1、先上传文件
            ObjectMetadata meta = new ObjectMetadata();
            meta.setContentLength(input.available());
            // 这里如果有请求头就设置一下,没有就不设置
            if (!StringUtils.isEmpty(param.getContextType())) {
                meta.setContentType(param.getContextType());
                System.out.println(param.getContextType());
            }
            //第二个参数可以修改为目录+文件名
            s3client.putObject(bucket, param.getFileName(), input, meta);
            //2、生成文件的外链
            GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucket, param.getFileName());
            Calendar nowTime = Calendar.getInstance();
            nowTime.add(Calendar.MINUTE, 60 * 24 * param.getMaxDays());
            request.setExpiration(nowTime.getTime());

            URL url = s3client.generatePresignedUrl(request);
            //替换文件路径 换为最后项目需要使用的路径
            //是否需要这个代码看你的业务
            res.setFileUrl(url.toString().replace("http://.com/", "https:///"));
            if ("".equals(res.getFileUrl())) {
                res.setErrorMsg("ceph上传文件失败");
                return res;
            }

            //3、保存到DB中.
            //TODO 如果保存到DB异常怎么办?
            try {
                SysCephfile file = new SysCephfile();
                file.setCreateTime(LocalDateTime.now());
                file.setBlockName(BLOCK_NAME);
                file.setExpireTime(LocalDateTime.now().plusDays(param.getMaxDays()));
                file.setOriginFileName(param.getOriginFileName());
                file.setCephKey(param.getFileName());
                // param.getContextType()
                file.setUrl(res.getFileUrl());
                file.setFileId(0L);
                this.cephManager.saveCephFile(file);
            } catch (Exception ex) {
                //根据SysCephfile的信息去删除ceph中的图片
                //s3client.deleteObject();
                LogUtil.error(ex, "保存oss存储对象异常");
            }

            res.setOk(true);
            res.setErrorMsg("");
        } catch (Exception ex) {
            res.setErrorMsg("上传ceph异常," + ex.getMessage() + ex.getStackTrace());
        } finally {
            return res;
        }
    }

}

实体类

@Data
@EqualsAndHashCode(callSuper = false)
@TableName("sys_cephfile")
public class SysCephfile implements Serializable {

private static final long serialVersionUID = 1L;

            /**
            * 自增长主键
            */
            @TableId(value = "id", type = IdType.AUTO)
    @FieldName("自增长主键")
    private Long id;

            /**
            * 具体存储的blockname
            */
    @FieldName("具体存储的blockname")
    private String blockName;

            /**
            * 创建时间
            */
    @FieldName("创建时间")
    private LocalDateTime createTime;

            /**
            * 过期时间
            */
    @FieldName("过期时间")
    private LocalDateTime expireTime;

            /**
            * 原始文件名
            */
    @FieldName("原始文件名")
    private String originFileName;

            /**
            * 传ceph的key(对外)
            */
    @FieldName("传ceph的key(对外)")
    private String cephKey;

            /**
            * 上传后的url
            */
    @FieldName("上传后的url")
    private String url;

            /**
            * 具体归属的档案ID,是0就是没保存的.
            */
    @FieldName("具体归属的档案ID,是0就是没保存的.")
    private Long fileId;


}

最后的mapper层使用的是mybatisplus,没有任何代码,纯CRUD,就不贴出了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/727964.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MV-Map论文研读

MV-Map MV-Map: Offboard HD-Map Generation with Multi-view Consistency 论文&#xff1a;https://arxiv.org/pdf/2305.08851.pdf code&#xff1a;https://github.com/ZiYang-xie/MV-Map 代码未开源 总体网络结构 简述 论文首次提出以非车载的方式产生高精度地图。可以…

基于QT使用7z压缩与解压总结

1. 概述 本文主要讲述使用7z第三方工具对文件或文件夹进行加密压缩和解密解压相关方法。7z的全称7-Zip&#xff0c;是一款开源软件。&#xff08;资源主页&#xff1a;https://7-zip.org/&#xff09;2. 设计原理 本文主要使用7z.exe通过命令行来实现压缩与解压功能&…

数据库之MySQL字符集与数据库操作

目录 字符集 CHRARCTER SET 与COLLATION的关联 CHRARCTER SET 定义 基础操作 查看当前MySQL Server支持的 CHARACTER SET 查看特定字符集信息&#xff08;主要包含默认的COLLATION 与 MAXLEN&#xff09; COLLATION 定义 COLLATION后缀 基础操作 查看MySQL Server支持的…

C++教程(一)开发环境visual studio的安装——图文详细

一、visual studio下载地址&#xff1a; 1、百度网盘 链接&#xff1a;https://pan.baidu.com/s/1QJosSoAT7EumuvyjtC_1Iw?pwdwuqz 提取码&#xff1a;wuqz 2、官网下载 Visual Studio: 面向软件开发人员和 Teams 的 IDE 和代码编辑器 (microsoft.com)https://visualstudio.…

【Linux】vi编辑器的使用,要求能新建、编辑、保存一个文本文件。

&#xff08;1&#xff09;点击”应用程序”→ “附件”→“终端”&#xff0c;打开终端&#xff0c;在终端输入命令&#xff1a; [rootlocalhost root]#vi kk.c按 i 键&#xff0c;进入插入状态。 &#xff08;2&#xff09;输入以下C程序 #include<stdio.h>int main( …

【CEEMDAN-WOA-LSTM】完备集合经验模态分解-鲸鱼优化-长短时记忆神经网络研究(Python代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Java用native修饰的方法

今天看JDK ServerSocket源代码的时候&#xff0c;通过层层调用&#xff0c;到了用native修饰的方法&#xff0c;然后再也跟不下去了。 例如sun.nio.ch.Net类中下面的方法&#xff1a; Java方法如果用native修饰&#xff0c;就表示这个方法的实现不是用java实现的&#xff0c…

Element Ui Tree组件实现增、删、改、查、拖拽节点 的树形结构

介绍&#xff1a;首先组件 | Element官网某些功能都具备了&#xff0c;这里我就把这些功能结合在一起更完美的使用&#xff0c;其次编辑节点官网是没有实例&#xff0c;所以这里搞了一套较完整的功能&#xff0c;其次编辑和添加&#xff0c;这里直接使用了弹窗&#xff08;顾及…

单位列表单列出来,假(封装)组件

效果图&#xff1a; 因为每个页面都用到这个单位&#xff0c;所以把单位列表单列出来&#xff0c;假装是个封装的组件&#xff0c;在其他页面直接用。 源码&#xff1a; <template><div style"height: 48rem;overflow-y: scroll"><h4>单位列表<…

阿里云国际站代理商:阿里云是干什么的?阿里云app和建网站有什么关系?

标题&#xff1a;阿里云是干什么的&#xff1f;阿里云app和建网站有什么关系&#xff1f;   一、解析阿里云的业务范围   阿里云&#xff0c;作为阿里巴巴集团的关键业务板块&#xff0c;主要提供云计算、大数据、人工智能及其他信息化服务。通过其全球网络&#xff0c;阿里…

8 spring-boot访问静态资源

8.1 静态资源存放的位置 在资源目录下分别创建public和resources两个文件夹&#xff0c;static是一开始就存在的&#xff0c;静态资源可以存放在这三个文件夹中。当这三个文件夹同时出现相同的静态资源&#xff0c;如每个文件夹都有一个1.js时&#xff0c;则优先访问resources里…

vue 目录

vue学习资源 vue.js中文官网&#xff1a; http://cn.vuejs.org/ vue.js源码&#xff1a; https://github.com/vuejs/vue vue.js官方工具&#xff1a; https://github.com/vuejs vue.js英文官网&#xff1a; https://vuejs.org/ vue全家桶 介绍 介绍 【 Vue全家桶 Vue&#xff…

Vue3使用echarts仪表盘(gauge)

Documentation - Apache ECharts 可自定义设置以下属性 仪表盘数据源&#xff08;gaugeData&#xff09;&#xff0c;类型&#xff1a;Gauge[]&#xff0c;必传&#xff0c;默认 []容器宽度&#xff08;width&#xff09;&#xff0c;类型&#xff1a;number | string&#x…

web前端(二)

表格标签&#xff1a; <table> </table>按照这个顺序&#xff1a;一个可选的 <caption> 元素零个或多个的 <colgroup> 元素一个可选的 <thead> 元素下列任意一个&#xff1a;零个或多个 <tbody>零个或多个 <tr>一个可选的 <t…

轻量应用服务器5m支持多少人访问?

​  轻量应用服务器5m支持多少人访问?对于网站而言&#xff0c;服务器的带宽肯定是越大越好&#xff0c;但对于用户的钱包则相反&#xff0c;服务器的价格高低与带宽大小、类型也有很大的关系&#xff0c;我们只有选择到合适的带宽才能将轻量应用服务器显得更有性价比&#…

复习V2+V3之——01 前言回顾

前言 Vue的特点 采用组件化的模式&#xff0c;提高代码复用率&#xff0c;让代码更好维护 声明式编码&#xff0c;开发者无需直接操作DOM&#xff0c;提高开发效率 使用虚拟DOM Diff算法&#xff0c;尽量复用DOM节点 虚拟DOM&#xff08;Virtual DOM&#xff09;&#xff1…

JAVA 牛客网 NC32求平方根

题目如图&#xff1a; 先展示代码&#xff1a; import java.util.*;public class Solution {/*** 代码中的类名、方法名、参数名已经指定&#xff0c;请勿修改&#xff0c;直接返回方法规定的值即可*** param x int整型* return int整型*/public int sqrt (int x) {if(x<2)…

递归回溯两个例题:1.数组组合 2.在矩阵中搜索单词

题目1&#xff1a;组合 给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按 任何顺序 返回答案。 输入&#xff1a;n 4, k 2 输出&#xff1a; [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ] 解题思路&#xff1a; 1.定…

Mac苹果电脑也可以玩原神了,运行流程,nice~

最近发现了一个很棒的工具&#xff0c;他可以让你的 Mac 苹果电脑运行原神&#xff0c;而且画质和流畅度都是在线的&#xff0c;今天分享给大家 软件名字叫 playCover &#xff0c;根据作者的介绍这款软件最初就是国外的一位博主想在 Mac 上玩原神特意开发的一款软件&#xff…

代码随想录二刷 day45 | 动态规划 之 70. 爬楼梯 (进阶) 322. 零钱兑换 279.完全平方数

day45 70. 爬楼梯 &#xff08;进阶&#xff09;1. 确定dp数组以及下标的含义2.确定递推公式3.dp数组如何初始化4.确定遍历顺序5.举例来推导dp数组 322. 零钱兑换1. 确定dp数组以及下标的含义2.确定递推公式3.dp数组如何初始化4.确定遍历顺序5.举例推导dp数组 279.完全平方数1.…