SpringBoot3 + Element-Plus + Vue3 + TS 实现上传、拖拽上传到阿里云OSS视频、图片、文件以及对图片、视频的预览和删除OSS端图片(最新)

1、效果展示

1.1 界面展示

在这里插入图片描述

1.2 操作展示

在这里插入图片描述

1.3 上传展示

在这里插入图片描述

1.4 视频预览

在这里插入图片描述

1.5 图片预览

在这里插入图片描述

2、Vue3 实现代码

2.1 UploadFiles.vue

<!--
 * @Date: 2024-04-18 11:23:45
 * @LastEditors: zhong
 * @LastEditTime: 2024-04-20 15:13:44
 * @FilePath: \app-admin\src\components\UploadFile\UploadFiles.vue
-->
<template>
  <div class="file">
    <el-upload class="upload-demo" drag action="http://127.0.0.1:9999/api/file/upload"
      accept=".jpg,.jpeg,.png,.gif,.mp4,.avi" :on-success="fileSuccess" multiple :on-preview="handlePictureCardPreview"
      :on-remove="handleRemove" v-model:file-list="filePathList" :before-upload="beforeUpload"
      :on-progress="handleProgress" list-type="picture-card">
      <template #file="{ file }">
        <template v-if="(/\.(mp4|avi|mov)$/i).test(file.url)" class="video-container">
          <video ref="currentVideo" class="preview-video video-player" controls>
            <source :src="file.url" type="video/mp4" />
          </video>
          <label class="el-upload-list__item-status-label"><i
              class="el-icon el-icon--upload-success el-icon--check"><svg xmlns="http://www.w3.org/2000/svg"
                viewBox="0 0 1024 1024">
                <path fill="currentColor"
                  d="M406.656 706.944 195.84 496.256a32 32 0 1 0-45.248 45.248l256 256 512-512a32 32 0 0 0-45.248-45.248L406.592 706.944z">
                </path>
              </svg></i></label>
          <i class="el-icon el-icon--close"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
              <path fill="currentColor"
                d="M764.288 214.592 512 466.88 259.712 214.592a31.936 31.936 0 0 0-45.12 45.12L466.752 512 214.528 764.224a31.936 31.936 0 1 0 45.12 45.184L512 557.184l252.288 252.288a31.936 31.936 0 0 0 45.12-45.12L557.12 512.064l252.288-252.352a31.936 31.936 0 1 0-45.12-45.184z">
              </path>
            </svg></i>
          <i class="el-icon--close-tip">delete 键可删除</i>
          <span class="el-upload-list__item-actions">
            <span class="el-upload-list__item-preview" @click="playVideo(file.url)"><i class="el-icon el-icon--zoom-in">
                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
                  <path fill="currentColor"
                    d="m795.904 750.72 124.992 124.928a32 32 0 0 1-45.248 45.248L750.656 795.904a416 416 0 1 1 45.248-45.248zM480 832a352 352 0 1 0 0-704 352 352 0 0 0 0 704m-32-384v-96a32 32 0 0 1 64 0v96h96a32 32 0 0 1 0 64h-96v96a32 32 0 0 1-64 0v-96h-96a32 32 0 0 1 0-64z">
                  </path>
                </svg></i>
            </span>
            <span class="el-upload-list__item-delete" @click="deleteMove(file)"><i class="el-icon el-icon--delete">
                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
                  <path fill="currentColor"
                    d="M160 256H96a32 32 0 0 1 0-64h256V95.936a32 32 0 0 1 32-32h256a32 32 0 0 1 32 32V192h256a32 32 0 1 1 0 64h-64v672a32 32 0 0 1-32 32H192a32 32 0 0 1-32-32zm448-64v-64H416v64zM224 896h576V256H224zm192-128a32 32 0 0 1-32-32V416a32 32 0 0 1 64 0v320a32 32 0 0 1-32 32m192 0a32 32 0 0 1-32-32V416a32 32 0 0 1 64 0v320a32 32 0 0 1-32 32">
                  </path>
                </svg></i>
            </span>
          </span>
        </template>
      </template>
      <el-icon class="el-icon--upload cloud"><upload-filled /></el-icon>
      <div class="el-upload__text" style="line-height: 22px;">
        Drop file here or <em>click to upload</em>
      </div>
      <!-- <template #tip>
        <div class="el-upload__tip">
          files with a size less than 100MB
        </div>
      </template> -->
    </el-upload>
  </div>
  <el-dialog v-model="dialogVisibleVideo" style="text-align: center;" title="视频预览">
    <video class="preview-video video-player" width="400px" controls>
      <source :src="dialogVideoUrl" type="video/mp4" />
    </video>
  </el-dialog>
  <el-dialog v-model="dialogVisibleImage" style="text-align: center;" title="图片预览">
    <img w-full :src="dialogImageUrl" alt="Preview Image" width="400px" height="auto">
  </el-dialog>
</template>

<script setup lang="ts">
import { UploadFilled } from '@element-plus/icons-vue'
import { ref, Ref } from 'vue';
import { ElMessage, type UploadProps } from 'element-plus'
import { deleteFileApi } from '@/api/upLoadFile/upLoadFile';
interface Video {
  name: string;
  url: string;
}
const filePathList: Ref<Video[]> = ref([
  {
    name: "a",
    url: "https://img1.baidu.com/it/u=836505309,2852067569&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=499"
  },
  {
    name: "b",
    url: "https://b0.bdstatic.com/1ea54e9fbe51ef1ef7a7f7a9efc97335.jpg@h_1280"
  }
])
// 判断上传文件大小
const beforeUpload = (file: File) => {
  const isLt10M = file.size / 1024 / 1024 < 100;
  if (!isLt10M) {
    ElMessage.error('上传文件大小不能超过 100MB!');
  }
  return isLt10M;
};
// 上传进度
const handleProgress = (event: ProgressEvent, file: File) => {
  const percentage = ((event.loaded / event.total) * 100).toFixed(2);
  ElMessage.success(`文件 ${file.name} 上传中:${percentage}%`);
};
// 上传文件成功写入List
const fileSuccess = (res: any) => {
  filePathList.value[filePathList.value.length - 1].url = res.data;
}

// 查看视频
const dialogVideoUrl = ref('')
const dialogVisibleVideo = ref(false)
const playVideo = (url: string) => {
  // 更新当前视频 URL
  dialogVideoUrl.value = url;
  dialogVisibleVideo.value = true;
}
// 删除视频
const deleteMove = async (file: any) => {
  filePathList.value = filePathList.value.filter(item => item.url !== file.url);

  // 实际删除oss函数
  let url = file.url;
  const lastSlashIndex = url.lastIndexOf('/');
  let name = url.substring(url.lastIndexOf('/') + 1);
  let date = url.substring(url.lastIndexOf('/', lastSlashIndex - 1), lastSlashIndex)
  let res = await deleteFileApi(date + "," + name);
  if (res && res.code == 200) {
    // 信息提示
    ElMessage.success(res.msg);
  } else {
    ElMessage.error("删除失败,请重试!");
  }
}
const dialogImageUrl = ref('')
const dialogVisibleImage = ref(false)

// 删除照片
const handleRemove: UploadProps['onRemove'] = async (uploadFile) => {
  // 获取 url参数
  let url: string = <string>uploadFile.url;
  // 实际删除oss函数
  // 找到日期部分的起始位置(倒数第二个斜杠的下一个位置)
  const lastSlashIndex = url.lastIndexOf('/');
  let name = url.substring(url.lastIndexOf('/') + 1);
  let date = url.substring(url.lastIndexOf('/', lastSlashIndex - 1), lastSlashIndex)
  let res = await deleteFileApi(date + "," + name);
  if (res && res.code == 200) {
    // 信息提示
    ElMessage.success(res.msg);
  } else {
    ElMessage.error("删除失败,请重试!");
  }
}
// 查看照片
const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
  dialogImageUrl.value = uploadFile.url!
  dialogVisibleImage.value = true;
}
</script>

<style lang="scss" scoped>
::v-deep(.el-upload-dragger) {
  height: 148px !important;
  padding-top: 10px;
  padding-bottom: 0px;
  margin-bottom: 0px;
}


::v-deep(.cloud) {
  margin-bottom: 0px;
}

.file {
  display: flex;
  flex-wrap: wrap;
}

.upload-demo {
  width: 100%;

  .el-upload__tip {
    text-align: center;
  }
}

.video-player {
  width: 100%;
  /* 视频宽度占满容器 */
  height: 100%;
  /* 视频高度占满容器 */
  object-fit: cover;
  /* 使用 cover 保持视频比例,可能会裁剪视频 */
}
</style>

2.2 UseUpLoadFile.vue

<template>
  <UploadFiles></UploadFiles>
</template>
<script setup lang="ts">
  import UploadFiles from '@/components/UploadFile/UploadFiles.vue';
</script>

2.3 upLoadFile.ts

/*
 * @Date: 2024-04-18 12:35:49
 * @LastEditors: zhong
 * @LastEditTime: 2024-04-20 14:27:08
 * @FilePath: \app-admin\src\api\uploadFile\upLoadFile.ts
 */
import http from "@/http";
// 新增
export const ossUploadFileApi = () => {
	return http.put(`/api/file/upload/`);
}
//删除图片
export const deleteFileApi = (fileName: string) => {
	console.log(fileName);
	return http.put(`/api/file/upload/${fileName}`);
} 

2.4 http.ts

/*
 * @Date: 2024-03-30 12:37:05
 * @LastEditors: zhong
 * @LastEditTime: 2024-04-16 20:27:33
 * @FilePath: \app-admin\src\http\index.ts
 */
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from "axios";
import { ElMessage } from "element-plus";

// axios 请求配置
const config = {
    // baseURL:'http://localhost:8080',
    baseURL: '/api',
    timeout: 1000
}
// 定义返回值类型
export interface Result<T = any> {
    code: number;
    msg: string;
    data: T;
}

class Http {
    // axios 实例
    private instance: AxiosInstance;
    // 构造函数初始化
    constructor(config: AxiosRequestConfig) {
        this.instance = axios.create(config);
        //定义拦截器
        this.interceptors();
    }
    private interceptors() {
        // axios 发送请求之前的处理
        this.instance.interceptors.request.use((config: InternalAxiosRequestConfig) => {
            // 在请求头部携带token
            // let token = sessionStorage.getItem('token');
            let token = '';
            if (token) {
                config.headers!['token'] = token;
                // 把 token 放到 headers 里面
                // (config.headers as AxiosRequestHeaders).token = token;
            }
            // console.log(config);
            return config;

        }, (error: any) => {
            error.data = {};
            error.data.msg = '服务器异常,请联系管理员!'
            return error;
        })
        // axios 请求返回之后的处理
        // 请求返回处理
        this.instance.interceptors.response.use((res: AxiosResponse) => {
            // console.log(res.data);
            if (res.data.code != 200) {
                ElMessage.error(res.data.msg || '服务器出错啦');
                return Promise.reject(res.data.msg || '服务器出错啦');
            } else {
                return res.data;
            }
        }, (error) => {
            console.log('进入错误!');
            error.data = {};
            if (error && error.response) {
                switch (error.response.status) {
                    case 400:
                        error.data.msg = "错误请求";
                        ElMessage.error(error.data.msg);
                        break;
                    case 401:
                        error.data.msg = "未授权,请登录";
                        ElMessage.error(error.data.msg);
                        break;
                    case 403:
                        error.data.msg = "拒绝访问";
                        ElMessage.error(error.data.msg);
                        break;
                    case 404:
                        error.data.msg = "请求错误,未找到该资源";
                        ElMessage.error(error.data.msg);
                        break;
                    case 405:
                        error.data.msg = "请求方法未允许";
                        ElMessage.error(error.data.msg);
                        break;
                    case 408:
                        error.data.msg = "请求超时";
                        ElMessage.error(error.data.msg);
                        break;
                    case 500:
                        error.data.msg = "服务器端出错";
                        ElMessage.error(error.data.msg);
                        break;
                    case 501:
                        error.data.msg = "网络未实现";
                        ElMessage.error(error.data.msg);
                        break;
                    case 502:
                        error.data.msg = "网络错误";
                        ElMessage.error(error.data.msg);
                        break;
                    case 503:
                        error.data.msg = "服务不可用";
                        ElMessage.error(error.data.msg);
                        break;
                    case 504:
                        error.data.msg = "网络超时";
                        ElMessage.error(error.data.msg);
                        break;
                    case 505:
                        error.data.msg = "http版本不支持该请求";
                        ElMessage.error(error.data.msg);
                        break;
                    default:
                        error.data.msg = `连接错误${error.response.status}`;
                        ElMessage.error(error.data.msg);
                }
            } else {
                error.data.msg = "连接到服务器失败";
                ElMessage.error(error.data.msg)
            }
            return Promise.reject(error);
        })
    }
    // GET方法
    get<T = Result>(url: string, params?: object): Promise<T> {
        return this.instance.get(url, { params });
    }
    // POST方法
    post<T = Result>(url: string, data?: object): Promise<T> {
        return this.instance.post(url, data);
    }
    // PUT方法
    put<T = Result>(url: string, data?: object): Promise<T> {
        return this.instance.put(url, data );
    }
    // DELETE方法
    delete<T = Result>(url: string): Promise<T> {
        return this.instance.delete(url);
    }
}

export default new Http(config);

3、SpringBoot 实现代码

3.1 FileUploadController.java

package com.zhx.app.controller;

import com.zhx.app.utils.AliOssUtil;
import com.zhx.app.utils.ResultUtils;
import com.zhx.app.utils.ResultVo;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.util.UUID;

/**
 * @ClassName : FileUploadController
 * @Description : 文件上传相关操作
 * @Author : zhx
 * @Date: 2024-03-01 19:45
 */
@RestController
@RequestMapping("/api/file")
public class FileUploadController {
    @PostMapping("/upload")
    public ResultVo upLoadFile(MultipartFile file) throws Exception {
        // 获取文件原名
        String originalFilename = file.getOriginalFilename();
        // 防止重复上传文件名重复
        String fileName = null;
        if (originalFilename != null) {
            fileName = UUID.randomUUID() + originalFilename.substring(originalFilename.indexOf("."));
        }
        // 把文件储存到本地磁盘
//        file.transferTo(new File("E:\\SpringBootBase\\ProjectOne\\big-event\\src\\main\\resources\\flies\\" + fileName));
        String url = AliOssUtil.uploadFile(fileName, file.getInputStream());
        return ResultUtils.success("上传成功!", url);
    }

    @PutMapping("/upload/{fileName}")
    public ResultVo deleteFile(@PathVariable("fileName") String fileName) {
        System.out.println(fileName);
        if (fileName != null) {
            return AliOssUtil.deleteFile(fileName);
        }
        return ResultUtils.success("上传失败!");
    }
}

3.2 AliOssUtil.java

package com.zhx.app.utils;

import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.PutObjectRequest;
import com.aliyun.oss.model.PutObjectResult;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @ClassName : AliOssUtil
 * @Description : 阿里云上传服务
 * @Author : zhx
 * @Date: 2024-03-1 20:29
 */
@Component
public class AliOssUtil {
    private static String ENDPOINT;

    @Value("${alioss.endpoint}")
    public void setENDPOINT(String endpoint) {
        ENDPOINT = endpoint;
    }

    private static String ACCESS_KEY;

    @Value("${alioss.access_key}")
    public void setAccessKey(String accessKey) {
        ACCESS_KEY = accessKey;
    }

    private static String ACCESS_KEY_SECRET;

    @Value("${alioss.access_key_secret}")
    public void setAccessKeySecret(String accessKeySecret) {
        ACCESS_KEY_SECRET = accessKeySecret;
    }

    private static String BUCKETNAME;

    @Value("${alioss.bucketName}")
    public void setBUCKETNAME(String bucketName) {
        BUCKETNAME = bucketName;
    }

    public static String uploadFile(String objectName, InputStream inputStream) {
        String url = "";
        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(ENDPOINT, ACCESS_KEY, ACCESS_KEY_SECRET);
        try {
            // 创建PutObjectRequest对象。
            // 生成日期文件夹路径,例如:2022/04/18
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd");
            String dateStr = dateFormat.format(new Date());
            PutObjectRequest putObjectRequest = new PutObjectRequest(BUCKETNAME, dateStr + "/" + objectName, inputStream);
            // 如果需要上传时设置存储类型和访问权限,请参考以下示例代码。
            // ObjectMetadata metadata = new ObjectMetadata();
            // metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
            // metadata.setObjectAcl(CannedAccessControlList.Private);
            // putObjectRequest.setMetadata(metadata);

            // 上传文件。
            PutObjectResult result = ossClient.putObject(putObjectRequest);

            url = "https://" + BUCKETNAME + "." + ENDPOINT.substring(ENDPOINT.lastIndexOf("/") + 1) + "/" + dateStr + "/" + objectName;
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
        return url;
    }

    public static ResultVo deleteFile(String objectName) {
        System.out.println(objectName);
        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(ENDPOINT, ACCESS_KEY, ACCESS_KEY_SECRET);
        try {
            // 删除文件。
            System.out.println(objectName);
            System.out.println(objectName.replace(",", "/"));
            ossClient.deleteObject(BUCKETNAME, objectName.replace(",", "/"));
            return ResultUtils.success("删除成功!");
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
        return ResultUtils.error("上传失败!");
    }
}

3.3 yml

spring:
  # 设置文件上传大小
  servlet:
    multipart:
      max-file-size: 100MB
      max-request-size: 100MB
# 阿里云配置
alioss:
  endpoint: "https://oss-cn-beijing.aliyuncs.com"    # Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
  bucketName: "**********"  # 填写Bucket名称,例如examplebucket。
  access_key: "********************"  # 点击头像->Accesskey管理查看 秘钥
  access_key_secret: "********************" # 密码
  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
代码(使用Vue3和Element Plus): ``` <template> <div> <el-table :data="tableData" stripe> <el-table-column prop="id" label="ID"></el-table-column> <el-table-column prop="name" label="Name"></el-table-column> <el-table-column prop="age" label="Age"></el-table-column> </el-table> <el-pagination @current-change="handleCurrentChange" :current-page="currentPage" :page-size="pageSize" layout="total, prev, pager, next" :total="total"> </el-pagination> </div> </template> <script> import { ref } from 'vue'; import { getTableData } from '@/api/example'; export default { setup() { const currentPage = ref(1); const pageSize = ref(10); const total = ref(0); const tableData = ref([]); async function getData() { const params = { currentPage: currentPage.value, pageSize: pageSize.value, }; const res = await getTableData(params); if (res.code === 200) { tableData.value = res.data.list; total.value = res.data.total; } } function handleCurrentChange(page) { currentPage.value = page; getData(); } getData(); return { currentPage, pageSize, total, tableData, handleCurrentChange, }; }, }; </script> ``` 后代码(使用Spring Boot 2): ``` @GetMapping("/tableData") public CommonResult<PageResult<TableData>> getTableData(@RequestParam(required = false, defaultValue = "1") Integer currentPage, @RequestParam(required = false, defaultValue = "10") Integer pageSize) { PageResult<TableData> pageResult = tableDataService.getTableData(currentPage, pageSize); return CommonResult.success(pageResult); } ``` 其中,`TableData`为实体类,`PageResult`为分页结果类。`tableDataService`为对应的Service类,用于查询数据。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

她似晚风般温柔789

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值