springboot结合vue对本地文件及其文件夹进行操作

springboot结合vue对本地文件及其文件夹进行操作

今天自己做了一个小东西,就是springboot结合vue对本地文件及其文件夹进行操作,可以打开本地文件,打开本地的目录信息,下载本地文件,然后还对不同IP的访问做了一个限制,一个是添加了一个白名单,只有白名单上面的IP地址才能访问该指定的网站,另一个就是设置同一个IP地址在一分钟只能访问该IP地址10次,超过10次之后加入黑名单,只有一天之后才能继续访问。IP限制是两个不同的功能,这一篇文章只写对本地文件以及本地文件夹的操作。

前端页面代码:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

    <script src="./js/vue.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <!-- 引入样式 -->
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
    <!-- 引入组件库 -->
    <script src="https://unpkg.com/element-ui/lib/index.js"></script>
</head>
<body>
    <div id="root">
        <ul  id="fileNames" v-if="false">
            <li th:each="fileName : ${fileNames}" th:text="${fileName}"></li>
        </ul>
        <el-tree default-expand-all @node-contextmenu="handleContextMenu" :data="fileNames" :props="defaultProps"  @node-click="handleNodeClick"></el-tree>

<!--        右击菜单信息-->
        <el-dialog
                :title="fileInfo.absolutePath"
                :visible.sync="dialogVisible"
                width="30%">
            <el-card class="box-card">
                <div style="margin-top: 5px; cursor: pointer" class="text item" v-if="!fileInfo.directory" @click="openFile()">打开文件</div>
                <div style="margin-top: 5px; cursor: pointer" class="text item" v-if="!fileInfo.directory" @click="downloadFile()">下载文件</div>
                <div style="margin-top: 5px; cursor: pointer" class="text item" v-if="!fileInfo.directory" @click="downloadFile2()">下载文件(方式二)</div>
                <el-popover
                        v-if="fileInfo.directory"
                        placement="right"
                        width="400"
                        trigger="click">
                    <el-input v-model="newFolderName" placeholder="请输入文件夹名称" size="mini" @blur="cancelCreateFolder" @keyup.enter.native="newFolder" ></el-input>
                    <div style="margin-top: 5px; cursor: pointer" class="text item" slot="reference">新建文件夹</div>
                </el-popover>
            </el-card>
        </el-dialog>
    </div>
</body>
</html>

<script>
    // 通过DOM获取后端传递的数据
    var messageFromBackend = []
    messageFromBackend = document.getElementById("fileNames").innerText;
    const vm = new Vue({
        el:'#root',   // el用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串
        data:{          // data 中用于存储数据,数据供el指定的容器去使用,这些值我们暂时先写成一个对象
            fileNames: [],
            defaultProps: {
                children: 'children',
                label: 'name'
            },
            dialogVisible: false,
            dialogVisible2: false,
            fileInfo: [],
            newFolderName: '',
            clickNum: 0,
            fileContent: ''
        },
        methods: {
            handleNodeClick(data) {
                let _self = this
                if (!data.directory) {
                    // 发起带参数的 GET 请求
                    axios.post('http://localhost:8888/openFile/', data).then(response => {
                    }).catch(error => {
                        console.error('Error fetching data:', error);
                    });
                    this.doubleClick = false;
                    return false;
                }
                const size = _self.fileNames.indexOf(data)
                data.children = null
                // 发起带参数的 GET 请求
                axios.post('http://localhost:8888/getFiles/', data).then(response => {
                    if (response.data.length === 0) {
                        this.$message.error("该目录下已没有其他目录或文件!")
                        return false;
                    }
                    let tempPath = response.data[0].absolutePath
                    const parts = tempPath.split("\\");
                    const result = parts.slice(0, -1).join("\\");
                    this.setAgeRecursively(_self.fileNames, result, response.data);
                    }).catch(error => {
                        console.error('Error fetching data:', error);
                    });

            },
            setAgeRecursively(data, targetName, newAge) {
                for (let i = 0; i < data.length; i++) {
                    if (data[i].absolutePath === targetName) {
                        data[i].children = newAge;
                        return true; // 找到目标并赋值后退出递归
                    }
                    if (data[i].children && data[i].children.length > 0) {
                        if (this.setAgeRecursively(data[i].children, targetName, newAge)) {
                            return true; // 找到目标并赋值后退出递归
                        }
                    }
                }
                return false; // 未找到目标对象
            },
            handleContextMenu(node, event) {
                this.fileInfo = event;
                this.dialogVisible = true;
            },
            // 打开文件
            openFile() {
                let dataInfo = this.fileInfo
                // 发起带参数的 POST 请求
                axios.post('http://localhost:8888/openFile/', dataInfo).then(response => {
                }).catch(error => {
                    console.error('Error fetching data:', error);
                });
                this.fileInfo = []
                this.dialogVisible = false;
            },
            // 下载文件
            downloadFile() {
                let dataInfo = this.fileInfo
                // 发起带参数的 POST 请求
                axios.post('http://localhost:8888/downloadFile/', dataInfo).then(response => {
                }).catch(error => {
                    console.error('Error fetching data:', error);
                });
                this.fileInfo = []
                this.dialogVisible = false;
            },
            downloadFile2() {
                let dataInfo = this.fileInfo
                // 发起带参数的 POST 请求
                axios.post('http://localhost:8888/downloadFile2/', dataInfo).then(response => {
                    responseFileData = response.config.data;
                    responseFileData = JSON.parse(responseFileData)
                    // 创建一个 Blob 对象
                    const blob = new Blob([response.data], {
                        type: response.headers["content-type"]
                    });
                    // 创建一个下载链接并点击
                    const url = URL.createObjectURL(blob);
                    const link = document.createElement("a");
                    link.href = url;
                    link.download = responseFileData.name; // 替换为实际的文件名和扩展名
                    link.click();
                    // 释放资源
                    URL.revokeObjectURL(url);

                }).catch(error => {
                    console.error('Error fetching data:', error);
                });
                this.fileInfo = []
                this.dialogVisible = false;
            },
            // 新建文件夹
            newFolder() {
                let dataInfo = this.fileInfo
                dataInfo.newFolderName = this.newFolderName; console.log(dataInfo)
                dataInfo.children = null
                // 发起带参数的 POST 请求
                axios.post('http://localhost:8888/createFolder/', dataInfo).then(response => {
                }).catch(error => {
                    console.error('Error fetching data:', error);
                });
                this.fileInfo = []
                this.newFolderName = '';
                this.dialogVisible = false;
            },
            // 新建文件夹时input标签失去焦点后触发的方法
            cancelCreateFolder() {
                this.newFolderName = '';
                this.dialogVisible = false;
                this.fileInfo = []
            }
        },
        created() {
            messageFromBackend = JSON.parse(messageFromBackend)
            this.fileNames = messageFromBackend;
        }
    })
</script>

前提:准备工作,创建好实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class FileInfo {
    private String name;            // 文件或者文件夹的名称
    private String absolutePath;    // 文件或者文件夹的绝对路径
    private FileInfo children;      // 文件夹下面的子文件夹
    private String newFolderName;   // 新建文件夹时的名称
    private boolean isDirectory;    // 判断是否为文件夹
    public void setIsDirectory(boolean directory) {
        isDirectory = directory;
    }
}

功能一:后端把指定目录下面的数据发送给前端,然后前端接收到数据,并展示到页面上

后端代码

@Controller
public class FileShowController {
    @Autowired
    private ObjectMapper objectMapper;
    
@GetMapping("/info")
public String showFiles(Model model) throws JsonProcessingException {
    // 指定要展示的文件夹路径
    String path = "C:/";
    List<FileInfo> fileList = new ArrayList<>();
    File directory = new File(path);
    // 这里是通过 for 循环读取这个路径下的所有文件
    if (directory.isDirectory()) {	// 判断这个路径是否为文件夹
        File[] files = directory.listFiles();	// 获取到这个路径下的所有文件信息
        if (files != null) {
            for (File file : files) {
                FileInfo fileInfo = new FileInfo();
                fileInfo.setName(file.getName());	// 获取这个文件的名称
                fileInfo.setIsDirectory(file.isDirectory());	// 判断这个文件是否为一个文件夹
                fileInfo.setAbsolutePath(file.getAbsolutePath());  // 获取这个文件的绝对路径
                fileList.add(fileInfo);	// 把这个文件的信息保存在定义好的 List 数组里面
            }
        }
    }
    // 这里是将 Java 对象转换为一个 json 格式的字符串
    String info = objectMapper.writeValueAsString(fileList);
    // 将文件名列表传递给模板,用于前端接收
    model.addAttribute("fileNames", info);
    // 返回的是前端页面的名称,作用就是去到 fileList.html 这个页面
    return "fileList";
}
}

注意:因为这里是需要去到页面,所以这个类的注解是 @Controller,而不是 @RestController

前端代码

页面代码

<html xmlns:th="http://www.thymeleaf.org">
<!-- 注意,前端想接收到数据必须引入这个模块 -->

<div id="root">
 		<!-- 这里是接收后端发送过来数据的,然后通过 js 把数据存放到 vue 里面的 data 中 -->
 		<!-- 所以这个不需要显示,所有需要显示的信息,由 element-ui 的组件来展示 -->
        <ul  id="fileNames" v-if="false">
            <li th:each="fileName : ${fileNames}" th:text="${fileName}"></li>
        </ul>
        <el-tree default-expand-all :data="fileNames" :props="defaultProps"  @node-click="handleNodeClick"></el-tree>
</div>

js 代码

<script>
    // 通过DOM获取后端传递的数据
    // 这里就是通过标签获取到标签中的数据,然后把数据保存在 messageFromBackend  数组里面
    var messageFromBackend = []
    messageFromBackend = document.getElementById("fileNames").innerText;
    const vm = new Vue({
        el:'#root',
        data:{          
            fileNames: [],
            defaultProps: {
                children: 'children',
                label: 'name'
            }
        },
        methods: {
            handleNodeClick(data) {
                
            }
        },
        created() {
        	// 这里就是把数据存放到 vue 中,然后 vue 就可以使用后端传递过来的数据了
            messageFromBackend = JSON.parse(messageFromBackend)
            this.fileNames = messageFromBackend;
        }
    })
</script>

问题1:大家可以思考一下为什么要这样接收数据

功能二:点击一个目录,则会展示该目录下的内容

@PostMapping("/getFiles")
@ResponseBody
public List<FileInfo> getFiles(@RequestBody FileInfo info) throws JsonProcessingException {
    String path = info.getAbsolutePath();
    ArrayList<FileInfo> fileList = new ArrayList<>();
    File directory = new File(path);
    if (directory.isDirectory()) {
        File[] files = directory.listFiles();
        if (files != null) {
            for (File file : files) {
                FileInfo fileInfo = new FileInfo();
                fileInfo.setName(file.getName());
                fileInfo.setIsDirectory(file.isDirectory());
                fileInfo.setAbsolutePath(file.getAbsolutePath());
                fileList.add(fileInfo);
            }
        }
    }
    return fileList;
}

功能三:打开一个点击的文件

@PostMapping("/openFile")
public String openFile(@RequestBody FileInfo fileInfo) {
	// 先创建一个 byte 数组,然后把读取到的文件内容保存在这个数组里面
	// 这个数组作为一个临时文件
    byte[] fileData = new byte[0];
    String filePath = fileInfo.getAbsolutePath();
    System.err.println(filePath);

    try {
        File file = new File(filePath);
        fileData = new byte[(int) file.length()];

        try (FileInputStream inputStream = new FileInputStream(file)) {
            inputStream.read(fileData);
        }

        // 现在 fileData 包含了文件的字节数组数据
        // 可以在这里处理 fileData
        try {
            File tempFile = File.createTempFile("temp", ".tmp");
            try (FileOutputStream outputStream = new FileOutputStream(tempFile)) {
                outputStream.write(fileData);
            }

            filePath = tempFile.getAbsolutePath();

            // 根据操作系统不同,设置合适的命令来打开文件
            String osName = System.getProperty("os.name").toLowerCase();
            String command;

            if (osName.contains("win")) {
                command = "cmd /c start " + filePath;
            } else if (osName.contains("mac") || osName.contains("nix") || osName.contains("nux") || osName.contains("bsd")) {
                command = "open " + filePath;
            } else {
                throw new UnsupportedOperationException("Unsupported operating system");
            }

            Process process = Runtime.getRuntime().exec(command);
            process.waitFor();
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }

    } catch (IOException e) {
        e.printStackTrace();
    }
    return "fileList";
}

功能四:在一个目录下面创建一个文件夹

@PostMapping("/createFolder")
@ResponseBody
public String createFolder(@RequestBody FileInfo fileInfo) {
	// 得到的是需要创建文加的名称
    String folderName = fileInfo.getNewFolderName();
	// 得到的文件夹的路劲
    String parentDirectoryPath = fileInfo.getAbsolutePath(); // 替换为你要创建文件夹的父目录路径

    Path newFolderPath = Paths.get(parentDirectoryPath, folderName);

    try {
    	// 创建文件夹
        Files.createDirectories(newFolderPath);
        return "Folder created: " + newFolderPath;
    } catch (IOException e) {
        e.printStackTrace();
        return "Failed to create folder.";
    }
}

功能五:下载文件

方法一:下载到指定的路径

@PostMapping("/downloadFile")
    public void downloadFile(@RequestBody FileInfo fileInfo, HttpServletResponse response) throws IOException {
        String sourceFilePath = fileInfo.getAbsolutePath(); // 替换为你要下载的源文件路径
        String destinationDirectoryPath = "C:\\user\\新建文件夹"; // 替换为你要下载到的目标目录路径

        File sourceFile = new File(sourceFilePath);
        File destinationDirectory = new File(destinationDirectoryPath);

        if (sourceFile.exists() && destinationDirectory.exists() && destinationDirectory.isDirectory()) {
            Path destinationFilePath = Paths.get(destinationDirectoryPath, sourceFile.getName());

            try {
                Files.copy(sourceFile.toPath(), destinationFilePath);
                response.getWriter().write("File downloaded to: " + destinationFilePath);
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
        }
        System.out.println("下载成功");
    }

方法二:通过浏览器下载到自己选中的路径

@PostMapping("/downloadFile2")
public ResponseEntity<Resource> downloadFile2(@RequestBody FileInfo fileInfo, HttpServletResponse response) throws IOException {
    String filename = fileInfo.getName();
    String filePath = fileInfo.getAbsolutePath();
    Path file = Paths.get(filePath);
    Resource resource = new FileSystemResource(file);

    HttpHeaders headers = new HttpHeaders();
    headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + filename);
    headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE);

    return ResponseEntity.ok()
            .headers(headers)
            .contentLength(resource.contentLength())
            .body(resource);
}

总结: 这样一个本地文件夹相关的操作已经简单的完成,然后因为我只是实现了一个简单的功能,一些细节方面没有处理,所以如果大家要用到实际当中,可以在此基础上再次进行优化,比如,把一些路径写到 application.yml 里面,然后再通过 @Value 注解引用,这样以后需要改动一些路径也会方便许多,然后数据返回的格式也可以统一一下,然后前端页面成功与失败可以提供一个提示信息

这里我对上面传输数据的方式表达一下我的见解,因为我也只是一个编程菜鸟,考虑不足望搭建见谅,如果大家有不同的想法,也欢迎大家在评论区留言!!!

因为我浏览器访问的是后端的地址,然后再由后端的返回值返回到前端页面,所以这里就不能返回 json 数据,只能返回前端页面名称,然后数据传输方式我就选择了 model.addAttribute 的方式,然后前端我就选择了使用 thymeleaf 模块来接收数据。然后再通过原生 js 获取到节点上的数据,然后把数据传输到 vue

提供的改善方法:
大家可以在浏览器直接访问页面,然后在 vue 的创建生命周期函数中对后端发起请求,这样后端就可以发送 json 数据,然后再由后端的 vue 接收,这样就换成了大家常用的数据发送以及接收的方式

如果大家有其他见解,欢迎大家给我留言!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值