Vue+SpringBoot实现仿网盘项目

news2024/11/13 19:39:12

目录

一、效果展示

二、前端代码

三、后端代码及核心解释

四、进阶开发与思路


一、效果展示

1.1读取文件夹内的文件

1.2删除功能

1.3 上传文件

1.4 文件下载

对应的网盘实际地址与对应下载内容:


二、前端代码

2.1 创建vue项目(需要有vuex与router)并引入elementUi

npm i element-ui -S

2.2设置 VUEX(index.js):

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

// store/index.js
export default new Vuex.Store({
  state: {
    selectedFiles: []
  },
  mutations: {
    ADD_TO_SELECTED(state, fileName) {
      state.selectedFiles.push(fileName);
    },
    REMOVE_FROM_SELECTED(state, fileName) {
      const index = state.selectedFiles.indexOf(fileName);
      if (index !== -1) {
        state.selectedFiles.splice(index, 1);
      }
    },
    REMOVE_ALL(state) {
      state.selectedFiles = [];
    }
   
  },
  // ...
});

组件:FileCard Component:

<template>
  <div class="file-cards" style="line-height: normal;">
    <div v-for="(file, index) in fileList" :key="index" class="file-card" @click="toggleControlsAndSelect(index)">
      <i :class="[file.isDir ? 'el-icon-folder-opened' : 'el-icon-document', 'file-icon']"></i>
      <div class="file-name">{{ file.name }}</div>
      <!-- 添加勾选框 -->
      <el-checkbox-group v-model="selectedFiles" @change="handleGroupChange">
        <el-checkbox :label="file.name" class="checkbox"></el-checkbox>
      </el-checkbox-group>
    </div>
  </div>
</template>

<script>
import { mapState, mapMutations } from 'vuex';

export default {
  props: {
    fileList: {
      type: Array,
      required: true,
    },
  },
  computed:{
    ...mapState(['selectedFiles']),
  },
  data() {
    return {
      // selectedFiles: [], // 用于存储被选中的文件名
    };
  },
  methods: {
    ...mapMutations(['ADD_TO_SELECTED', 'REMOVE_FROM_SELECTED']),

    handleGroupChange(values) {
      values.forEach(value => this.ADD_TO_SELECTED(value));
      this.fileList.filter(file => !values.includes(file.name)).forEach(file =>
        this.REMOVE_FROM_SELECTED(file.name)
      );

    },

    toggleControlsAndSelect(index) {

      const fileName = this.fileList[index].name;

      if (this.selectedFiles.includes(fileName)) {
        this.REMOVE_FROM_SELECTED(fileName);
      } else {
        this.ADD_TO_SELECTED(fileName);
      }

    },
  },
};
</script>

<style scoped>
.file-cards {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}

.file-card {
  background-color: #fff;
  border-radius: 4px;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  margin: 10px;
  padding: 20px;
  width: 200px;
  cursor: pointer;
  transition: all 0.3s;
}

.file-card:hover {
  box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.12);
}

.file-icon {
  font-size: 50px;
  color: #409eff;
}

.file-name {
  text-align: center;
  margin-top: 10px;
}
</style>

根组件:App.vue

Html:

<template>
    <div id="Pan" style="border: 1px solid black; min-height: 90%; background-color: rgb(250, 250, 250);">


        <!-- 操作板块 -->
        <div id="Operate"
            style="height: 50px; line-height: normal; border-top: 1px solid rgb(250, 250, 250); margin-top: 25px; ">

            <el-upload class="upload-demo" action="/api/file/upload" :on-change="handleChange" :file-list="fileList" :show-file-list="showFileList"
                style=" display: inline-block;">
                <el-button type="primary">
                    <i class="el-icon-upload2"></i>
                    上传</el-button>
            </el-upload>



            <el-button type="success" style="margin-left: 10px;" @click="downloadSelectedFiles">
                <i class="el-icon-download"></i>
                下载</el-button>
            <el-button type="primary" plain>
                <i class="el-icon-share"></i>
                分享</el-button>
            <el-button type="danger" plain @click="deleteFile">
                <i class="el-icon-delete"></i>
                删除</el-button>

        </div>
        <!-- 导航板块 -->
        <div id="navigation">
            <el-breadcrumb separator-class="el-icon-arrow-right" style="padding-left: 10%; line-height: normal;">
                <el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
                <el-breadcrumb-item :to="{ path: '/' }">我的网盘</el-breadcrumb-item>
            </el-breadcrumb>

        </div>


        <!-- 全选文件 -->
        <div style="height: 35px; background-color: white; border: 1px solid rgb(230, 230, 230);">
            <el-checkbox :indeterminate="isIndeterminate" v-model="checkAll" @change="handleCheckAllChange"
                style="float: left; line-height: 35px; padding-left: 20%;">全选文件</el-checkbox>
        </div>


        <div id="FileList">
            <file-cards :file-list="sampleFiles" @update-file-selection="handleFileSelectionUpdate"></file-cards>
        </div>

    </div>
</template>


Javascript:

<script>
import FileCards from '@/components/FileCards.vue';
import Cookies from 'js-cookie';
import { mapState, mapMutations } from 'vuex';
import axios from 'axios';

export default {

    computed: {
        ...mapState(['selectedFiles']),
    },
    components: {
        FileCards,
    },
    data() {
        return {
            sampleFiles: [
            ],
            // 文件全选
            isIndeterminate: false,
            checkAll: false,
            fileList: [],
            showFileList:false,
        };
    },

    methods: {
        ...mapMutations(['ADD_TO_SELECTED', 'REMOVE_FROM_SELECTED', 'SET_ALL_SELECTED', 'REMOVE_ALL']),

        ListUserFiles() {
            const id = Cookies.get("userId");
            if (id === null) {
                this.$notify({
                    title: '警告',
                    message: '请还未登录,无法使用本功能',
                    type: 'warning'
                });
                return;
            }
        },

        addToSelected(fileName) {

            if (!this.selectedFiles.includes(fileName)) {
                this.selectedFiles.push(fileName);
            }
        },

        removeFromSelected(fileName) {
            const index = this.selectedFiles.indexOf(fileName);
            if (index !== -1) {
                this.selectedFiles.splice(index, 1);
            }
        },
        handleFileSelectionUpdate(fileName, isChecked) {
            if (isChecked) {
                this.addToSelected(fileName);
            } else {
                this.removeFromSelected(fileName);
            }
        },

        // 全选文件
        handleCheckAllChange() {

            if (this.selectedFiles.length === this.sampleFiles.length) {
                this.REMOVE_ALL();
                return;
            }
            this.REMOVE_ALL();
            this.sampleFiles.forEach(file => {
                this.ADD_TO_SELECTED(file.name);
            });

        },

        // 上传文件
        handleChange() {


            console.log(this.fileList);

            this.$message.success('上传成功');
            this.getPanlist();

        },


        // 获取网盘文件列表
        getPanlist() {
            axios.get('api/file/list').then((Response) => {
                this.sampleFiles = Response.data.data;
            })

        },


        // 删除文件
        deleteFile() {
            axios.delete('api/file', {
                data: {
                    fileNames: this.selectedFiles
                }
            }).then((response) => {
                this.REMOVE_ALL();
                this.getPanlist();
                console.log(response);
            });
        },


        downloadSelectedFiles() {
            console.log(this.selectedFiles);

            // 确保有文件被选中
            if (this.selectedFiles.length === 0) {
                alert("请选择要下载的文件!");
                return;
            }

            axios({
                url: 'api/file/download',
                method: 'POST',
                responseType: 'blob', // 告诉axios我们希望接收的数据类型是二进制流
                data: {
                    fileNames: this.selectedFiles
                }
            }).then(response => {
                // 创建一个a标签用于触发下载
                let url = window.URL.createObjectURL(new Blob([response.data]));
                let link = document.createElement('a');
                link.href = url;
                // 如果你知道文件名,可以设置下载文件名
                link.setAttribute('download', 'download.zip');
                document.body.appendChild(link);
                link.click();
                // 清理
                document.body.removeChild(link);
            });
        },


    },
    mounted() {
        this.ListUserFiles();
        this.getPanlist();
    },


};
</script>

css:

<style scoped>
#FileList {
    margin-top: 20px;
}

#upload {
    float: left;
}
</style>

三、后端代码及核心解释

额外的依赖:

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</version>
        </dependency>

用以构造

3.1 返回类

//结果类
public class Result<T> {
    // 状态码常量
    public static final int SUCCESS = 200;
    public static final int ERROR = 500;
    
    private int code; // 状态码
    private String message; // 消息
    private T data; // 数据

    // 构造函数,用于创建成功的结果对象
    private Result(int code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }

    // 成功结果的静态方法
    public static <T> Result<T> success(T data) {
        return new Result<>(SUCCESS, "Success", data);
    }


    // 错误结果的静态方法
    public static <T> Result<T> error(String message) {
        return new Result<>(ERROR, message, null);
    }

    // 错误结果的静态方法,可以传入自定义的状态码
    public static <T> Result<T> error(int code, String message) {
        return new Result<>(code, message, null);
    }

    // 获取状态码
    public int getCode() {
        return code;
    }

    // 设置状态码
    public void setCode(int code) {
        this.code = code;
    }

    // 获取消息
    public String getMessage() {
        return message;
    }

    // 设置消息
    public void setMessage(String message) {
        this.message = message;
    }

    // 获取数据
    public T getData() {
        return data;
    }

    // 设置数据
    public void setData(T data) {
        this.data = data;
    }

    // 用于转换为Map类型的方法,方便序列化为JSON
    public Map<String, Object> toMap() {
        Map<String, Object> map = new HashMap<>();
        map.put("code", code);
        map.put("message", message);
        map.put("data", data);
        return map;
    }
}

规范化后端返回Response的数据

由于本次上传都是小文件,后端限制在10MB以内.

@Configuration
public class servletMultipartConfigElement {
    @Bean
    public javax.servlet.MultipartConfigElement multipartConfigElement() {

        MultipartConfigFactory factory = new MultipartConfigFactory();
        // 设置单个文件的最大大小
        factory.setMaxFileSize(DataSize.ofMegabytes(10));

        // 设置整个请求的最大大小
        factory.setMaxRequestSize(DataSize.ofMegabytes(100));

        return factory.createMultipartConfig();

    }
}

3.2 获取用户的文件内容

    // 获取文件内容
    @GetMapping("/list")
    public Result getListByUserId() {

//        TODO:后期以JWT鉴权方式,获取Token中的USerID
        int id = 8;
        File directory = new File("E:\\ProjectReal\\AI WIth WEB SHell\\Pan" + File.separator + id);
        if (!directory.exists()) {
            boolean mkdirs = directory.mkdirs();
            if (mkdirs){
                Result.success("网盘创建成功");
            }else {
                Result.error("网盘创建失败");
            }
            return Result.error("异常");
        }

        // 直接将 fileList 转换为 JSONArray
        JSONArray jsonArray = new JSONArray();
        
        File[] files = directory.listFiles();

        if (files != null) {
            for (File file : files) {
                JSONObject fileObj = new JSONObject();
                fileObj.put("name", file.getName());
                fileObj.put("isDir", file.isDirectory());
                fileObj.put("selected", false);
                jsonArray.add(fileObj);
            }
        }

        return Result.success(jsonArray);
    }

关键点在于通过java的IO与fastjson依赖构造出对应的JSON格式并返回

3.3 下载功能

@PostMapping("/download")
    public ResponseEntity<?> downloadSelectedFiles(@RequestBody FileNamesDto fileNamesDto) throws IOException {

        List<String> fileNames = fileNamesDto.getFileNames();

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ZipOutputStream zos = new ZipOutputStream(baos);

        for (String fileName : fileNames) {
            File file = new File("E:\\ProjectReal\\AI WIth WEB SHell\\Pan" + File.separator + "8" + File.separator + fileName);
            if (file.exists()) {
                try (FileInputStream fis = new FileInputStream(file)) {

                    ZipEntry zipEntry = new ZipEntry(fileName);
                    zos.putNextEntry(zipEntry);
                    byte[] bytes = new byte[1024];
                    int length;
                    while ((length = fis.read(bytes)) >= 0) {
                        zos.write(bytes, 0, length);
                    }
                    zos.closeEntry();
                }
                catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
        zos.close();
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        headers.setContentDispositionFormData("attachment", "download.zip");

        return new ResponseEntity<>(baos.toByteArray(), headers, HttpStatus.OK);
    }

关键点在于,下载时候,不同的文件对应的请求头的MIME是不一样的,所以将文件先压缩后下载时候就只有一个文件格式为zip格式。

 3.4 删除功能


    @DeleteMapping()
    public Result deleteFile(@RequestBody FileNamesDto fileNamesDto) {
        List<String> fileNames = fileNamesDto.getFileNames();
        int id = 8;

        for (String fileName : fileNames) {
            File file = new File("E:\\ProjectReal\\AI WIth WEB SHell\\Pan" + File.separator + id + File.separator + fileName);
            if (!file.exists()) {
                return Result.error("文件不存在");
            }
            if (file.isDirectory()){
                deleteDirectory(file);
            }else {
                boolean delete = file.delete();
            }
        }
        return Result.success("删除完成");
    }
    public static void deleteDirectory(File directory) {

        if (directory.exists()) {
            File[] entries = directory.listFiles();
            if (entries != null) {
                for (File entry : entries) {
                    if (entry.isDirectory()) {
                        deleteDirectory(entry);
                    } else {
                        entry.delete();
                    }
                }
            }
        }
        directory.delete();

    }

注意:对于非空的directory是无法直接进行删除的,所以通过isDir判断如果是目录时候,则进行递归删除。将所有子文件都删除后再对目录进行删除.

3.5 上传功能

 @PostMapping("/upload")
    public ResponseEntity<?> upload(@RequestParam("file") MultipartFile file) {
        try {
            // 检查文件是否为空
            if (file.isEmpty()) {
                return ResponseEntity.badRequest().body("文件为空");
            }
            // 获取上传文件的原始文件名
            String originalFileName = file.getOriginalFilename();
            // 创建目录(如果不存在)
            File directory = new File("E:\\ProjectReal\\AI WIth WEB SHell\\Pan\\8");
            if (!directory.exists()) {
                directory.mkdirs();
            }
            // 文件保存路径
            Path targetLocation = Path.of(directory.getAbsolutePath(), originalFileName);

            try (InputStream inputStream = file.getInputStream()) {
                Files.copy(inputStream, targetLocation, StandardCopyOption.REPLACE_EXISTING);
            }

            return ResponseEntity.ok("上传成功");
        } catch (IOException e) {
            return ResponseEntity.status(500).body("上传失败:" + e.getMessage());
        }
    }

 由于前端上传的格式是multipartFIle 格式,所以后端也需要相应类型的进行接收对其进行接收


四、进阶开发与思路

4.1 前端

1.可以通过设置拖拽区域实现,当拖拽文件到网盘内容区时,自动执行上传函数的功能。

2.对于大文件,可以单独写一个对应的大文件上传页面,并展示上传进度条。

4.2 后端

1.大文件上传,首先前端进行判断文件的大小,如果超过一定的大小,则调用大文件上传功能。这时候就需要实现分片上传与断点续传功能。

2.云盘网站用户的独立性,这次演示的是一个固定用户的网盘内容。在实现真正项目时候,可以通过jwt鉴权的方式,获取token中的userId,使得获取到每一个用户自己的网盘。

3.云盘存量的设置,可以在遍历用户文件时候计算总大小,并返回给前端展示。


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

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

相关文章

【C++】类和对象--类,实例化,this指针

文章目录 前言一、类1.1 类的定义1.2 类的书写和使用1.3 访问限定符1.4 类域 二、实例化2.1 实例化概念2.2 对象大小 三.this指针总结 前言 前面的几篇文章我们介绍了命名空间&#xff0c;inline&#xff0c;nullptr等C 中常见的的基础概念。今天的文章我们来介绍一些C中类与对…

一款24小时实时检测的六氟化硫气体泄漏报警系统

尽管当前工业生产模式越来越趋于自动化、智能化&#xff0c;但安全生产仍然是时下屡被提及的话题。在配电室等使用六氟化硫气体的众多领域中&#xff0c;由于气体泄漏而引发的中毒、火灾、爆炸、窒息事故仍高发频发。因此&#xff0c;安装六氟化硫气体泄漏报警监测系统仍是企业…

使用PEFT库进行ChatGLM3-6B模型的QLORA高效微调

PEFT库进行ChatGLM3-6B模型QLORA高效微调 QLORA微调ChatGLM3-6B模型安装相关库使用ChatGLM3-6B模型GPU显存占用准备数据集加载数据集数据处理数据集处理加载量化模型-4bit预处理量化模型配置LoRA适配器训练超参数配置开始训练保存LoRA模型模型推理合并模型使用微调后的模型 QLO…

UE5 本地化多语言方案

导入插件&#xff1a; https://www.unrealengine.com/marketplace/zh-CN/product/07e1d9bd9ced444c9b2a7e232161f74d​www.unrealengine.com/marketplace/zh-CN/product/07e1d9bd9ced444c9b2a7e232161f74d 打开测试关卡 打开插件下图目录&#xff0c;csv文件可以添加多个&…

MD5加密和注册页面的编写

MD5加密 1.导入包 npm install --save ts-md5 2.使用方式 import { Md5 } from ts-md5; //md5加密后的密码 const md5PwdMd5.hashStr("123456").toUpperCase(); 遇见的问题及用到的技术 注册页面 register.vue代码 <template><div class"wappe…

模型评估、交叉验证

目录 一、模型评估&#xff1a;二、交叉验证&#xff1a; 一、模型评估&#xff1a; 模型评估用来检验模型的预测精度。 首先数据集分为训练集和测试集两部分&#xff0c;使用训练集进行模型的训练&#xff0c;使用测试集进行模型的评估。 注意&#xff0c;模型评估阶段应该…

A股强反之后继续下单,难道是主力再使障眼法?

今天的A股太不对劲了&#xff0c;主力再使障眼法&#xff0c;很多散户都被成功迷惑&#xff0c;市场有望出现芙蓉出水&#xff0c;不废话&#xff0c;直奔主题&#xff1a; 1、今天两市低开再次探底&#xff0c;太不对劲了&#xff0c;昨天主力大幅流入&#xff0c;今天又跑了…

IT入门知识第十部分《IT职业发展》(10/10)

IT职业发展的深度解析&#xff1a;技能、路径与规划 在当今这个快速发展的数字化时代&#xff0c;IT行业不仅是技术革新的前沿阵地&#xff0c;也是职业发展和个人成长的沃土。本文将深入探讨IT行业的职业发展&#xff0c;包括职业路径的选择、技能提升的策略、以及职业规划的…

FastAPI 学习之路(三十七)元数据和文档 URL

实现前的效果 那么如何实现呢&#xff0c;第一种方式如下&#xff1a; from routers.items import item_router from routers.users import user_router""" 自定义FastApi应用中的元数据配置Title&#xff1a;在 OpenAPI 和自动 API 文档用户界面中作为 API 的…

迅为3A5000LoongArch架构核心主板支持国产麒麟、统信、以及实时系统翼辉SylixOS

性能强 采用全国产龙芯3A5000处理器&#xff0c;基于龙芯自主指令系统 (LoongArch)的LA464微结构&#xff0c;并进一步提升频率&#xff0c;降低功耗&#xff0c;优化性能。 桥片 采用龙芯 7A2000&#xff0c;支持PCIE 3.0、USB 3.0和 SATA 3.0.显示接口2 路、HDMI 和1路 VGA&a…

昆法尔The Quinfall在Steam上怎么搜索 Steam上叫什么名字

昆法尔The Quinfall是一款全新的MMORPG&#xff0c;在中世纪的深处&#xff0c;参与独特的战斗和沉浸式的故事&#xff0c;有几十个不同的职业。而游戏中的战斗系统更是丰富多彩&#xff0c;无论是陆地激战、海上鏖战还是城堡围攻&#xff0c;都能让玩家感受到前所未有的刺激和…

pytorch实现水果2分类(蓝莓,苹果)

1.数据集的路径&#xff0c;结构 dataset.py 目的&#xff1a; 输入&#xff1a;没有输入&#xff0c;路径是写死了的。 输出&#xff1a;返回的是一个对象&#xff0c;里面有self.data。self.data是一个列表&#xff0c;里面是&#xff08;图片路径.jpg&#xff0c;标签&…

如何分辨AI生成的内容?AI生成内容检测工具对比实验

检测人工智能生成的文本对各个领域的组织都提出了挑战&#xff0c;包括学术界和新闻界等。生成式AI与大语言模型根据短描述来进行内容生成的能力&#xff0c;产生了一个问题&#xff1a;这篇文章/内容/作业/图像到底是由人类创作的&#xff0c;还是AI创作的&#xff1f;虽然 LL…

【数据库】Redis主从复制、哨兵模式、集群

目录 一、Redis的主从复制 1.1 主从复制的架构 1.2 主从复制的作用 1.3 注意事项 1.4 主从复制用到的命令 1.5 主从复制流程 1.6 主从复制实现 1.7 结束主从复制 1.8 主从复制优化配置 二、哨兵模式 2.1 哨兵模式原理 2.2 哨兵的三个定时任务 2.3 哨兵的结构 2.4 哨…

MT3047 区间最大值

思路&#xff1a; 使用哈希表map和set&#xff08;去重&#xff09;维护序列 代码&#xff1a; #include <bits/stdc.h> using namespace std; const int N 1e5 10; int n, k, A[N]; map<int, int> mp; // 元素出现的次数 set<int> s; // 维护出现…

Android平台GB28181记录仪在电网巡检抢修中的应用和技术实现

技术背景 在探讨Android平台GB28181设备接入端在电网巡检抢修优势之前&#xff0c;我们已经在执法记录仪、智能安全帽、智能监控、智慧零售、智慧教育、远程办公、明厨亮灶、智慧交通、智慧工地、雪亮工程、平安乡村、生产运输、车载终端等场景有了丰富的经验积累&#xff0c;…

linux创建定时任务

crontab方式 先查看是否有cron systemctl status crond 没有的话就安装 yum install cronie 打开你的crontab文件进行编辑。使用以下命令打开当前用户的crontab文件&#xff1a; crontab -e * * * * * /export/test.sh >> /export/test.log 2>&1/export/test.s…

什么是量化机器人?它能来作些什么?一篇文章带你了解!

在科技日新月异的今天&#xff0c;我们经常会听到一些听起来高大上的词汇&#xff0c;比如“人工智能”、“大数据”和“量化交易”。而在这其中&#xff0c;“量化机器人”更是一个让人既好奇又略感神秘的存在。今天&#xff0c;我们就用通俗易懂的语言&#xff0c;一起来揭开…

通知notification

通知 权限&#xff1a;manifest.xml&#xff0c;可以不提前写&#xff0c;后面写代码时显示缺少点击添加即可。 <uses-permission android:name"android.permission.VIBRATE"/>//振动权限 <uses-permission android:name"android.permission.POST_NOT…

洛谷 7.10 数数

Vanya and Books - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) ac代码 #include<bits/stdc.h> typedef long long ll;#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0) const ll N1e3; using namespace std;int main() {IOS;ll x;cin>>x;ll ans0,px…