效果演示

在这里插入图片描述

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

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

前端

ElementUI
文件上传的页面使用的是ElementUI的
在这里插入图片描述
下面是index.vue页面,有点小bug,不过在我坚持不懈的努力下(大概15h吧哈哈哈),终于给他解决了,只能说,感觉这是“精通前端”的第一步
然后之后发现一个问题就是如果用户在文件还没有上传完毕之前,就直接点击提交,那么就会导致文件的丢失,所以之后我们添加一个文件上传中的一个全局锁定,保证此时用户不能进行提交操作
在这里插入图片描述

页面代码如下

        <!-- :on-preview="groupPreview" -->
<template&gt;
    <div&gt;
      <el-upload
        class="upload-demo"
        :action="uploadAction"
        :on-remove="handleRemove"
        :on-success="handleSuccess"
        :on-error="handleError"
        :before-upload="beforeUpload"
        :on-preview="groupPreview"
        :file-list="fileListTemp"
        :on-progress="testFile"
        &gt;
        <el-button size="small" type="primary">点击上传</el-button>
        <div slot="tip" class="el-upload__tip">只能上传jpg/png/pdf文件,且不超过20M<br>
            上传完毕之后请审核一下您上传的文件在确认是否提交
        </div>
      </el-upload>
    </div>
</template>
<script setup name="uploadImage">
import { ref, defineProps, onMounted, defineEmits } from 'vue';
import { ElMessage,ElLoading  } from 'element-plus';
const props = defineProps(['modelValue'])
const fileList = ref([])
let fileListTemp = ref([])
const urls = ref([])
const uploadAction = ref('/merchant/api/common/upload/image')
const emits = defineEmits(['update:modelValue','input'])

onMounted(() => {
    setDefaultFileList()
})
const setDefaultFileList = () => {
    //这里modelValue就是v-model传递过来的files
    console.log("----props.modelValue----");
    console.log(props.modelValue);
    console.log("------fileListTemp--");
    if (props.modelValue &amp;&amp; props.modelValue.length > 0) {
        fileListTemp.value = []
        props.modelValue.forEach(element => {
            let index = element.indexOf("|");
            fileListTemp.value.push({
                name: element.substr(0,index), 
                url: element.substr(index+1),
            })
        });
        console.log(fileListTemp)
        console.log("--------");
    }
}
const handleRemove = (file, fileList) => {
    fileListTemp.value = []
    emits("update:modelValue", fileListTemp);
}

let loading = null
const testFile = ()=>{
    loading =  ElLoading.service({
        lock:true,
        text:'文件上传中...'
    })
}

const handleSuccess = (response, file, fileList) => {
            let fileTemp = response.data.name+"|"+response.data.url;
            console.log("--------fileTemp---------")
            console.log(fileTemp);
            console.log("-----------------")
            fileListTemp.value.push({
                fileTemp
            });
    
    // fileListTemp.value.push(response.data.url);
    console.log("---------文件集合fileListTemp.value--------")
    console.log(fileListTemp.value);
    console.log("-----------------")
    emits("update:modelValue", fileListTemp);
    loading.close()
}

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:8080/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);
        // console.log(result)
        res.send({
          code: 200,
          msg: "图片上传成功",
          data: {
            name: result.data[0].name, //结构为:name | url
            url: result.data[0].url, // url
          },
        });
      } 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,就不贴出了

数据存储格式

我们的这些文件数据存储在MongoDB中的,格式字符串,如下

“files”: [“QQ截图20230508204810.png|https://xxxxx”,“QQxxx.jpg|https://xxxxx”]
我们的数据结构就是一个字符数组,并且我们把数组 | 前面内容设定为文件名称,后面的为文件路径,之所以专门设计是由于业务求导致,我也不想这么设计。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
我的理解前端会将先从数据库查询出来的数据存放到这个大数组中,之后我们直接使用里面对应的数据就好了。
其中,我们的uploadImage他本身是一个内嵌页面,是在script setup里面引入的,大概如下

<script setup name="certificateinfo">
const uploadImage = defineAsyncComponent(() => import('@/components/uploadImage/index.vue'));
</script>

我们在需要使用这个上传页面的地方通过如下方式引入

	<div class="width-full">
                <el-form-item label="" :prop="'certificationList.'+index+'.files'" class="form-item-upload" :rules="[{ required: false, message: '请上传资质附件扫描件', trigger: 'change' }]">
                    <slot name="label">
                        <div class="flex-row">
                            <label class="el-form-item__label required">资质附件扫描</label>
                            <span class="el-form-item__label">资质附件扫描件 (原件或加盖公章复印件,该图片将会展示用户,请确保图片清晰度)</span>
                            <!-- <span class="example-btn">示例</span> -->
                        </div>
                    </slot>
                    <!-- <uploadImage v-model="item.pics"></uploadImage> -->
                    <uploadImage v-model="item.files"></uploadImage>
                </el-form-item>
            </div>

原文地址:https://blog.csdn.net/Zhangsama1/article/details/131581933

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_22848.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注