系统中的文件存储方案

项目中经常要用到文件存储
方案1: 文件传给后端,后端存到本地,当静态资源
方案2: 文件传给后端,后端上传到文件服务器(如minio)
方案3: 前端从后端获取到文件服务器的账户和密码,然后前端直接上传到文件文件服务器,
本篇案例为 前后不分离代码

方案1:
前端代码(和方案2 公用一套前端代码,方案1后端代码不包含删除)
index.html 放在resource/static/web目录下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
<!--    <meta name="viewport" content="width=device-width, initial-scale=1.0">-->
    <title>上传文件</title>
    <script src="https://unpkg.com/vue/dist/vue.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>
    <!-- 引入axios -->
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
<div id="app">
    <el-upload
            class="file-upload"
            ref="upload"
            action="http://localhost:8633/api/upload"
            :on-preview="handlePreview"
            :on-remove="handleRemove"
            :before-remove="beforeRemove"
            :on-success="handleSuccess"
            multiple
            :limit="fileLimit"
            :on-exceed="handleExceed"
            :file-list="fileList">
        <el-button size="small" type="primary">点击上传</el-button>
        <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
    </el-upload>
    <div label="需要删除的链接" prop="author">
        <el-input v-model="deleteUrl" ></el-input>
        <el-button type="primary" @click="onDelete">删除</el-button>
    </div>
</div>
</body>
<script>
    new Vue({
    
    
        el: "#app",
        data() {
    
    
            return {
    
    
                fileLimit: 3,
                fileList: [],
                fileUrl: '',
                deleteUrl:'http://localhost:9000/mall/1628847099991_20210813173139_th.png'
            }
        },
        methods: {
    
    
            handleRemove(file, fileList) {
    
    
            },
            onDelete() {
    
    
                axios.get('http://localhost:8633/api/delete?fileName='+ this.deleteUrl)
                    .then(response => (this.info = response))
                    .catch(function (error) {
    
     // 请求失败处理
                        console.log(error);
                    })
            },
            handlePreview(file) {
    
    
            },
            handleExceed(files, fileList) {
    
    
                this.$message.warning(`当前限制选择 ${
     
     this.fileLimit} 个文件,本次选择了 ${
     
     files.length} 个文件,共选择了 ${
     
     files.length + fileList.length} 个文件`)
            },
            beforeRemove(file, fileList) {
    
    
                return this.$confirm(`确定移除 ${
     
     file.name}`)
            },
            handleSuccess(response) {
    
    
                this.url = response
                this.$emit('onUpload')
                this.$message.warning('上传成功')
            },
            clear() {
    
    
                console.log('就看看运行没有')
            }
        }
    })
</script>
<style>
</style>
</html>

后端代码:
1 设置路由

@Configuration
public class SpringMvcConfig implements WebMvcConfigurer {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/api/file/**").addResourceLocations("classpath:/static/img/");
        registry.addResourceHandler("/**").addResourceLocations("classpath:/static/web/");
    }
}

//设置跳转
@Controller
public class IndexController {

    @RequestMapping("/")
    public String index() {
        return "redirect:/index.html";
    }
}
    @CrossOrigin
    @PostMapping("api/upload")
    public String fileUpload(MultipartFile file) throws Exception {
    
    
        String filePath = System.getProperty("user.dir") + "\\springboot_17_minio\\target\\classes\\static\\img";
        String fileName = System.currentTimeMillis() + "_" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
        File imageFolder = new File(filePath);
        File f = new File(imageFolder, fileName + file.getOriginalFilename()
                .substring(file.getOriginalFilename().length() - 4));
        if (!f.getParentFile().exists())
            f.getParentFile().mkdirs();
        try {
    
    
            file.transferTo(f);
            String imgURL = "http://localhost:8080/api/file/" + f.getName();
            return imgURL;
        } catch (IOException e) {
    
    
            e.printStackTrace();
            return "";
        }
    }

方案二: 由后端传到minio服务器
docker安装minio服务 dockercompose文件如下

version: "3.7" 
services:
    minio:
        image: minio/minio 
        container_name: minio
        ports: 
            - 9000:9000
        #指定容器中的目录 /data
        command: server /data  
        environment:
            MINIO_ACCESS_KEY: admin  # 用户名
            MINIO_SECRET_KEY: admin123456  # 密码
           # MINIO_REGION_NAME: cn-home
           # MINIO_REGION_COMMENT: homelab
           # MINIO_BROWSER: on
           # MINIO_DOMAIN: oss.halobug.cn
        volumes:
            - ./data:/data
            - ./config:/root/.minio
        logging:
            options:
                max-size: "50M" # 最大文件上传限制
                max-file: "10"
            driver: json-file
         

前端代码(同方案一)

后端代码:
参考: https://blog.csdn.net/weixin_32801895/article/details/112249442
引入pom

<dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
    <version>3.0.10</version>
</dependency>

后端代码

@Slf4j
@Service
public class MinioServiceImpl implements MinioService {
    
    

    private  String url = "http://localhost:9000";
    // 存储桶名称
    private  String bucketName = "mall";
    // 访问的key
    private  String accessKey = "admin";
    // 访问的秘钥
    private  String secretKey = "admin123456";

    @Override
    public String uploadFile(MultipartFile file) {
    
    
        try {
    
    
            //创建一个MinIO的Java客户端
            MinioClient minioClient = new MinioClient(url, accessKey, secretKey);
            boolean isExist = minioClient.bucketExists(bucketName);
            if (isExist) {
    
    
                log.info("存储桶已经存在!");
            } else {
    
    
                //创建存储桶并设置只读权限
                minioClient.makeBucket(bucketName);
                minioClient.setBucketPolicy(bucketName, "*.*", PolicyType.READ_ONLY);
            }
            String filename = file.getOriginalFilename();
            // 设置存储对象名称
            String objectName = System.currentTimeMillis() + "_" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date())+ "_" + filename;

            // 使用putObject上传一个文件到存储桶中
            minioClient.putObject(bucketName, objectName, file.getInputStream(), file.getContentType());
            log.info("文件上传成功!");
            String fileUrl = url +"/"+bucketName +"/"+ objectName;
            return fileUrl;
        } catch (Exception e) {
    
    
            e.printStackTrace();
            log.error("文件上传出错!");
        }
        return null;
    }

    @Override
    public Boolean deleteFile(String objectName) {
    
    
        // 文件名称是 文件名称 前面的路径不包含
        //只有 1628847099991_20210813173139_th.png
        try {
    
    
            MinioClient minioClient = new MinioClient(url, accessKey, secretKey);
            String fileName = objectName.replace(url + "/" + bucketName + "/", "");
            minioClient.removeObject(bucketName, fileName);
            return true;
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        return false;
    }
}

项目结构:
在这里插入图片描述

**方案三: 从后端获取签名,使用带有签名的链接上传文件
前端代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <!--    <meta name="viewport" content="width=device-width, initial-scale=1.0">-->
    <title>上传文件</title>
    <script src="https://unpkg.com/vue/dist/vue.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>
    <!-- 引入axios -->
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
<div id="app">
    <el-upload
            class="file-upload"
            ref="upload"
            action="123"
            :http-request="fileUpload"
            :on-preview="handlePreview"
            :on-remove="handleRemove"
            :before-remove="beforeRemove"
            :on-success="handleSuccess"
            multiple
            :limit="fileLimit"
            :on-exceed="handleExceed"
            :file-list="fileList">
        <el-button size="small" type="primary">点击上传</el-button>
        <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
    </el-upload>
    <div label="需要删除的链接" prop="author">
        <el-input v-model="deleteUrl" ></el-input>
        <el-button type="primary" @click="onDelete">删除</el-button>
    </div>
</div>
</body>
<script>
    new Vue({
    
    
        el: "#app",
        data() {
    
    
            return {
    
    
                fileLimit: 3,
                fileList: [],
                fileUrl: '',
                deleteUrl:'http://localhost:9000/mall/1628847099991_20210813173139_th.png'
            }
        },
        methods: {
    
    
            fileUpload(res) {
    
    
                let file = res.file
                console.log(file)
                console.log('这里被触发了')
                let config = {
    
    
                    headers: {
    
    
                        'Content-Type': 'multipart/form-data'
                    }
                }
                axios.get('http://localhost:8633/api/getUploadUrl?fileName='+file.name)
                    .then(response => (
                        console.log('返回路径: '+response.data),
                          uploadUrl = response.data,
                        console.log('上传路径: '+uploadUrl),
                        axios.put(uploadUrl, file, config).then(res => (
                            console.log('这是上传后的结果'),
                                console.log(res),
                                console.log('文件路径为: '+uploadUrl.substr(0,uploadUrl.lastIndexOf('?')))
                        ))
                    ))
            },
            handleRemove(file, fileList) {
    
    
            },
           
            handlePreview(file) {
    
    
            },
            handleExceed(files, fileList) {
    
    
                this.$message.warning(`当前限制选择 ${
     
     this.fileLimit} 个文件,本次选择了 ${
     
     files.length} 个文件,共选择了 ${
     
     files.length + fileList.length} 个文件`)
            },
            beforeRemove(file, fileList) {
    
    
                return this.$confirm(`确定移除 ${
     
     file.name}`)
            },
            handleSuccess(response) {
    
    
                this.url = response
                this.$emit('onUpload')
                this.$message.warning('上传成功')
            }
        }
    })
</script>
<style>
</style>
</html>

后端代码:
参考: https://blog.csdn.net/xixiyuguang/article/details/119571051

    public String getUploadUrl(String fileName) {
    
    
        System.out.println(fileName);
        try {
    
    
            MinioClient minioClient = new MinioClient(url, accessKey, secretKey);
            Map<String, String> reqParams = new HashMap<String, String>();
            reqParams.put("response-content-type", "application/json");
            String objectName = System.currentTimeMillis() + "_" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + "_" + fileName;
            System.out.println("文件名称: "+objectName);
            String product = minioClient.getPresignedObjectUrl( Method.PUT , bucketName,objectName,60 * 60 * 24,reqParams);
            System.out.println(product); // 前端直传需要的url地址
            return  product;
        } catch (Exception e) {
    
    

        }
        return null;
    }

另外还有一种方式是 前端从后端获取账号和密码,然后前端自己上传,理论上这个是可以的,但是获取账号和密码的过程中,会泄露minio的账号和密码
参考 http://docs.minio.org.cn/docs/master/javascript-client-api-reference
https://www.jianshu.com/p/fd1dcb5ca6a8

Guess you like

Origin blog.csdn.net/xy3233/article/details/119729919