SpringBoot+vue文件上传下载预览大文件分片上传文件上传进度

news2024/9/28 9:23:52

文章目录

    • 学习链接
    • 上传文件
      • 前端
      • 后端代码
    • 下载文件
      • a标签下载
        • 前端代码
        • 后台代码
      • 动态a标签下载
        • 前端代码
      • axios + 动态a标签
        • 前端代码
      • 浏览器直接输入
    • 预览文件
      • 前端代码
      • 后端代码
    • 分片上传
      • 前后端分别md5加密
        • spark-md5
        • commons-codec
      • 分片上传实现
        • 前端代码
        • 后端代码

学习链接

Blob & File
spark-md5根据文件内容生成hash

上传文件

前台

  • 整个过程,就是在使用FormData 添加 上File(这个Blob),并且key要和后台的名字对应上
  • 在点击上传按钮开始上传之前,使用了URL.createObjectURL(File)创建blobUrl,给了img标签作图片预览
  • 上传完毕后,将input file的value置为空。若将input file置为空,则此时不能再从input file中获取file了,得等下次再选择图片才能获得file,将它置为空的目的是为了下次选择同样的图片,也能触发input file的change事件

后台

  • 后台仅仅就是用MultipartFile声明接收即可,可以使用@RequestParam注解 或 @RequestPart注解
  • 调用MultipartFile#transferTo保存文件
  • 可以从MultipartFile#getInputStream中获取流,比如上传到OSS。

在这里插入图片描述
前端控制台
在这里插入图片描述
后端控制台
在这里插入图片描述

前端

<template>
    <div>
        选择文件: <input type="file" ref="fileInputRef" @change="selectFile" multiple> <!-- 使用multiple属性,可选择多个文件 -->
        <br/>
        <img v-if="imgUrl" :src="imgUrl" alt="" style="width:54px;height:54px;">
        <el-button v-if="imgUrl" type="primary" @click="uploadFile">上传</el-button>

        <hr/>
    </div>
</template>

<script>
import axiosInstance from '@/utils/request.js'
import axios from 'axios'
export default {
    name: 'File',
    data() {
        return {
            imgUrl:''
        }
    },
    methods: {
        selectFile() {

            let file = this.$refs['fileInputRef'].files[0]
            console.log(file)

            // 上传前, 可以预览该图片
            let blobUrl = URL.createObjectURL(file)
            this.imgUrl = blobUrl

        },
        uploadFile() {

            // 因为可能选择多个文件, 所以这里是个数组
            let file = this.$refs['fileInputRef'].files[0]

            let formData = new FormData()

            formData.append('mfile', file) // 必须和后端的参数名相同。(我们看到了, 其实就是把blob文件给了formData的一个key)
            formData.append("type", 'avatar')

            // 可以有下面2种方式, 来上传文件
            /* axiosInstance
                .post('http://127.0.0.1:8083/file/uploadFile',formData, {headers: {'a':'b'}})
                .then(res => {
                    console.log('响应回来: ',res);
                }) */
            axiosInstance({ // 这种传参方式, 在axios的index.d.ts中可以看到
                url:'http://127.0.0.1:8083/file/uploadFile',
                method:'post',
                data: formData, // 直接将FormData作为data传输
                headers: {
                    'a':'b' // 可携带自定义响应头
                }
            }).then(res => {
                console.log('响应回来: ',res);
            })

            console.log(this.$refs['fileInputRef'].value); // C:\fakepath\cfa86972-07a1-4527-8b8a-1991715ebbfe.png
            // 上传完文件后, 将value置为空, 以避免下次选择同样的图片而不会触发input file的change事件。
            // (注意清空value后,将不能再从input file中获取file,而原先的file仍然能够使用)
            this.$refs['fileInputRef'].value = ''
        }
    }
}
</script>

<style>

</style>

后端代码

@PostMapping("uploadFile")
public Object uploadFile(@RequestPart("mfile")MultipartFile multipartFile,@RequestPart("type") String type) throws IOException {

    System.out.println(multipartFile.getClass());
    System.out.println(type);

    // 源文件名
    String originalFilename = multipartFile.getOriginalFilename();
    // 内容类型
    String contentType = multipartFile.getContentType();
    // 文件是否为空(无内容)
    boolean empty = multipartFile.isEmpty();
    // 文件大小
    long size = multipartFile.getSize();
    // 文件的字节数据
    byte[] bytes = multipartFile.getBytes();
    // 获取文件的字节输入流
    InputStream inputStream = multipartFile.getInputStream();
    // 将文件保存到指定路径下
    multipartFile.transferTo(new File("d:/Projects/practice/test-springboot/src/main/resources/file/" + originalFilename));

    System.out.println(originalFilename);
    System.out.println(contentType);
    System.out.println(empty);
    System.out.println(size);
    System.out.println(bytes.length);

    HashMap<String, Object> data = new HashMap<>();
    data.put("data", "ok");
    return data;
}

下载文件

a标签下载

在这里插入图片描述

前端代码

<template>
    <div>
       <a href="http://127.0.0.1:8083/file/downloadFile?filename=头像a.png">avatar3.png</a>
    </div>
</template>

<script>
import axiosInstance from '@/utils/request.js'
import axios from 'axios'
export default {
    name: 'File',
    data() {
        return {
        }
    },
    methods: {
        
    }
}
</script>

<style>

</style>

后台代码

@GetMapping("downloadFile")
public void downloadFile(@RequestParam("filename") String filename) throws Exception {

    // 告知浏览器这是一个字节流,浏览器处理字节流的默认方式就是下载
    // 意思是未知的应用程序文件,浏览器一般不会自动执行或询问执行。浏览器会像对待,
    // 设置了HTTP头Content-Disposition值为attachment的文件一样来对待这类文件,即浏览器会触发下载行为
    response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE);
    // ,该响应头指示回复的内容该以何种形式展示,是以内联的形式(即网页或者网页的一部分),还是以附件的形式下载并保存到本地。
    response.setHeader(HttpHeaders.CONTENT_DISPOSITION,"attachment;fileName="+ URLEncoder.encode(filename, "UTF-8"));
    File file = new File("d:/Projects/practice/test-springboot/src/main/resources/file/" + filename);

    ServletOutputStream ros = response.getOutputStream();

    FileInputStream fis = new FileInputStream(file);
    byte[] bytes = new byte[2 * 1024];
    int len = 0;
    while ((len = fis.read(bytes)) != -1) {
        ros.write(bytes, 0, len);
    }

    ros.flush();
    ros.close();
    fis.close()

}

动态a标签下载

  • 后台代码仍然用上面a标签下载的代码即可

在这里插入图片描述

前端代码

  • 只需要动态创建a标签,添加到body,然后手动调用js触发a标签的click事件,触发下载
  • 下载完成之后,将a标签移除
  • 整个过程a标签的样式都是display:none
<template>
    <div>
        <el-button type="success" @click="downloadFile">下载文件</el-button>
    </div>
</template>

<script>
import axiosInstance from '@/utils/request.js'
import axios from 'axios'
export default {
    name: 'File',
    data() {
        return {
        }
    },
    methods: {
        downloadFile() {
            let a = document.createElement('a')
            a.href = 'http://127.0.0.1:8083/file/downloadFile?filename=头像a.png'
            document.body.appendChild(a)
            a.style.display = 'none'
            a.click()
            document.body.removeChild(a)
        }
    }
}
</script>

<style>

</style>

axios + 动态a标签

  • 后台代码仍然用上面a标签下载的代码即可

在这里插入图片描述

前端代码

  • 收到后端的响应流 变成前端的 blob对象,然后使用浏览器的api,URL将blob对象创建为blobUrl,然后动态创建a标签,触发a标签点击完成下载
  • 使用/file/previewFile接口也是一样的效果,其实,只要后端往response里写数据,这里来就能用blob拿到
  • 下面的axios不要用封装的,因为要指定responseType:‘blob’,直接去拿数据
<template>
    <div>
        <el-button type="success" @click="downloadFile">下载文件</el-button>
    </div>
</template>

<script>
import axios from 'axios'
export default {
    name: 'File',
    data() {
        return {
        }
    },
    methods: {
        downloadFile() {
            axios({ // 使用原来的axios实例, 不能用封装的, 因为下面要直接拿响应的blob数据
                url:'http://127.0.0.1:8083/file/downloadFile?filename=头像a.png',
                method:'get',
                headers: {
                    'a':'b'
                },
                responseType: 'blob' // 这个可以在axios的index.d.ts中可以找到
            }).then(response=>{
                return response.data
            }).then(blob=>{
                console.log(blob);
                let ablob = new Blob([blob])
                let blobUrl = window.URL.createObjectURL(ablob)
                let tmpLink = document.createElement('a')
                tmpLink.style.display = 'none'
                tmpLink.href = blobUrl
                tmpLink.setAttribute('download','头像b.png')
                document.body.appendChild(tmpLink)
                tmpLink.click()
                document.body.removeChild(tmpLink)
                window.URL.revokeObjectURL(blobUrl)
            })
        }
    }
}
</script>

<style>

</style>

浏览器直接输入

直接在浏览器的地址栏输入,即可下载,同样用上面的地址即可:http://127.0.0.1:8083/file/downloadFile?filename=头像a.png

在这里插入图片描述

预览文件

  • 前端直接一个a标签即可,后端改个响应头即可。
  • 如果需要在页面中预览,则可使用上面提到的方法,获取到流之后,然后创建为blobUrl或dataUrl放入img标签的src属性即可。

在这里插入图片描述

前端代码

<template>
    <div>
        <a href="http://127.0.0.1:8083/file/previewFile?filename=头像a.png">头像a.png</a>
    </div>
</template>

<script>
import axios from 'axios'
export default {
    name: 'File',
    data() {
        return {
        }
    },
    methods: {
        
    }
}
</script>

<style>

</style>

后端代码

设置好响应头即可

@GetMapping("previewFile")
    public void previewFile(@RequestParam("filename") String filename) throws Exception {

    // 可使用ServletContext 通过文件名获取 媒体资源类型
    response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.IMAGE_PNG_VALUE);
    File file = new File("d:/Projects/practice/test-springboot/src/main/resources/file/" + filename);

    ServletOutputStream ros = response.getOutputStream();

    // 可参考: StreamUtils
    FileInputStream fis = new FileInputStream(file);
    byte[] bytes = new byte[4 * 1024];
    int len = 0;
    while ((len = fis.read(bytes)) != -1) {
        ros.write(bytes, 0, len);
    }

    ros.flush();
    ros.close();
	fis.close()
}

分片上传

前后端分别md5加密

在开始分片之前,先了解下md5加密,因为后面秒传需要用到,或者是其它场景需要标识到这个文件名文件二进制内容

  • md5的全称是message-digest algorithm 5(信息-摘要算法),它的作用是让大容量信息在用数字签名软件签署私人密匙前被"压缩"成一种保密的格式(就是把一个任意长度的字节串变换成一定长的大整数)。
  • MD5的典型应用是对一段信息(Message)产生信息摘要(Message-Digest),以防止被篡改。比如,在UNIX下有很多软件在下载的时候都有一个文件名相同,文件扩展名为.md5的文件,在这个文件中通常只有一行文本,大致结构如:MD5 (tanajiya.tar.gz) = 0ca175b9c0f726a831d895e269332461,这就是tanajiya.tar.gz文件的数字签名。MD5将整个文件当作一个大文本信息,通过其不可逆的字符串变换算法,产生了这个唯一的MD5信息摘要

spark-md5

  1. 安装spark-md5

    npm install spark-md5 --save
    
  2. 对字符串操作

    • 常规用法

      // 16进制哈希
      var hexHash = SparkMD5.hash('Hi there');        // d9385462d3deff78c352ebb3f941ce12
      // 再次执行, 仍然是同样的值
      var hexHash = SparkMD5.hash('Hi there');        // d9385462d3deff78c352ebb3f941ce12
      
      // 感觉这个没事撒用(应该就是原始的二进制数据,然后这个二进制数据转成了字符串形式)
      var rawHash = SparkMD5.hash('Hi there', true);  // Ù8TbÓÞÿxÃRë³ùAÎ\x12
      // 可以如下模拟以下上面这个过程,
      var fr = new FileReader()
      fr.read(new Blob([SparkMD5.hash('Hi there',true)]))
      // 看如下,获取了跟上面一样的结果
      console.log(fr.result) // Ù8TbÓÞÿxÃRë³ùAÎ\x12
      
    • 进阶用法

      var spark = new SparkMD5();
      
      spark.append('Hi');
      spark.append(' there');
      
      // d9385462d3deff78c352ebb3f941ce12,这个跟上面一样
      var hexHash = spark.end();       
      
      // Ԍُ  不知道是个什么玩意,跟上面直接调用SparkMD5.hash('Hi there', true);的结果不一样
      var rawHash = spark.end(true);     
      
  • 对文件操作
    对一个D:\documents\尚硅谷谷粒学院项目视频教程\项目资料.zip的1.18G的文件进行md5,获取的是:0efda58eb4bbb4ea4b69f9ac0d566075

    下面的方法摘自:npmjs仓库的spark-md5,可以体会一下这个递归在js里的用法:给FileReader绑定load事件,根据分片信息获取分片数据,并使用FileReader去read这个数据,从而绑定的load事件的函数就会执行,当处理完这个分片数据后,然后去触发下一个分片,直到所有的分片都read了(那么上传分片的时候,也可以使用下面的递归这么玩)。

    <template>
    	<input type="file" ref="fileInputRef" @change="getMd5($event.target.files[0])" />
    </template>
    
    export default {
    	methods: {
    		getMd5(file) {
                var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,
                    chunkSize = 10 * 1024 * 1024,
                    chunks = Math.ceil(file.size / chunkSize),
                    currentChunk = 0,
                    spark = new SparkMD5.ArrayBuffer(),
                    fileReader = new FileReader();
    
                fileReader.onload = function (e) {
                    console.log('read chunk nr', currentChunk + 1, 'of', chunks);
                    spark.append(e.target.result);
                    currentChunk++;
    
                    if (currentChunk < chunks) {
                        loadNext();
                    } else {
                        console.log('finished loading');
                        console.info('computed hash', spark.end());  // Compute hash
                    }
                };
    
                fileReader.onerror = function () {
                    console.warn('oops, something went wrong.');
                };
    
                function loadNext() {
                    var start = currentChunk * chunkSize,
                        end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;
    
                    fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
                }
    
                loadNext();
            },
    	}
    
    }
    
    

commons-codec

  • 需要先导入依赖

    <dependency>
        <groupId>commons-codec</groupId>
        <artifactId>commons-codec</artifactId>
        <version>1.12</version>
    </dependency>
    
  • 对字符串操作

    import org.apache.commons.codec.digest.DigestUtils;
    
    public static void main(String[] args) {
    
        String md5 = DigestUtils.md5Hex("Hi there");
        
        // d9385462d3deff78c352ebb3f941ce12, 与前端的md5结果一致
        System.out.println(md5);
        System.out.println(md5.length());
    }
    
  • 对文件二进制数据内容操作

    public static void main(String[] args) throws IOException {
    
        String s = DigestUtils.md5Hex(new FileInputStream(new File("D:\\documents\\尚硅谷谷粒学院项目视频教程\\项目资料.zip")));
       
        // 与前端计算结果一致
        // 0efda58eb4bbb4ea4b69f9ac0d566075
        System.out.println(s); 
    }
    

分片上传实现

这里只是实现分片上传的功能。会存在传参可能不合理,应该让要根据文件内容来标识到这个文件。后面需要根据具体的设计来改代码。比如设计表记录文件的每一个上传分片的记录,这样就能直到当前文件上传到第几个分片了,加入上传过程中分片失败了,下次上传前,先查询下这个文件上传到第几个分片了,然后就从那个分片后面开始上传。当根据文件内容计算的md5值能够在后台查到的话,那就直接算作秒传。

在这里插入图片描述

前端代码

<template>
    <div>
        <el-progress :text-inside="true" :stroke-width="26" :percentage="percentage" style="width: 350px;border-radius: 13px;border: 1px solid red;"></el-progress>
        <input type="file" ref="fileInputRef" />
        <el-button @click="uploadFile">上传文件</el-button>
    </div>
</template>

<script>
import axios from 'axios'
export default {
    name: 'File',
    data() {
        return {
            // 进度条
            percentage: 0
        }
    },
    
    methods: {
        async uploadFile() {
            const { files } = this.$refs['fileInputRef']
            let file = files[0]
            console.log(file.name);

            let size = file.size
            console.log(size);

            // 3 -  (0  1  2)

            let chunkSize = 10 * 1024 * 1024 // 1个分片 10M
            let start = 0 // 上传的开始位置  
            let index = 0 // 分片索引, 从0开始(0,1,2...)

            let totalFragmentCount = Math.ceil(size / chunkSize) // 总的分片数量

            while (true) {

                let end; // 当前分片的结束位置(不包括,开区间)

                if (start + chunkSize > size) { // 如果加上了一个分片大小,超出了文件的大小, 那么结束位置就是文件大小
                    end = size
                } else {
                    end = start + chunkSize // 如果加上了一个分片大小,没超出了文件的大小, 那么结束位置就是start加上分片大小
                }

                // 对file分片,分片完后, 给分片一个名字, 这个名字可以在后台获取为分片文件的真实名字
                let sfile = new File([file.slice(start, end)],`${file.name}-${index}`) 

                // 上传完这个分片后, 再走下面的代码
                await this.uploadFragmentFile(sfile, index, file.name, totalFragmentCount)

                index++
                if (end == size) { // 检查是否传完了, 传完了的话, 就跳出循环
                    break
                }

                // 开始位置
                start = end
            }

            console.log('发送合并文件请求');
            this.mergeFragmentFile(file.name)

        },

        // 上传分片文件(将切分的分片文件上传)
        uploadFragmentFile(sfile, index, realFilename, totalFragmentCount) {
            return new Promise((resolve, reject) => {
                let formData = new FormData()
                formData.append('sFile', sfile)
                formData.append('index', index)
                formData.append('realFilename', realFilename)
                console.log('sfile', sfile, index);
                axios({
                    url: 'http://localhost:8083/file/uploadSliceFile',
                    method: 'post',
                    data: formData,
                    headers: {
                        'a': 'b'
                    }
                }).then(res => {
                    console.log(`上传第${index}个分片成功`);
                    this.percentage = parseFloat(((index + 1) / totalFragmentCount * 100).toFixed(1))
                    resolve()
                })
            })

        },

        // 合并分片文件(当所有分片上传成功之后, 发送合并分片的请求)
        mergeFragmentFile(realFilename) {
            axios({
                url: 'http://localhost:8083/file/mergeFragmentFile',
                method: 'post',
                params: { realFilename },
                headers: {
                    'a': 'b'
                }
            }).then(res => {
                console.log('合并成功');
            })
        }
    }
}
</script>

<style></style>

后端代码

@PostMapping("uploadSliceFile")
public Object uploadSliceFile(@RequestParam("sFile")MultipartFile sFile,@RequestParam("realFilename") String realFilename, @RequestParam("index") Integer index) throws IOException {

    String md5 = DigestUtils.md5Hex(realFilename);
    System.out.println(realFilename);
    System.out.println(md5);
    System.out.println("分片名: " + sFile.getOriginalFilename());

    File dir = new File("d:/Projects/practice/test-springboot/src/main/resources/file/fragment/" + md5);
    if (!dir.exists()) {
        dir.mkdirs();
    }
    File sFileWithIndex = new File("d:/Projects/practice/test-springboot/src/main/resources/file/fragment/" + md5 + "/" + index);
    sFile.transferTo(sFileWithIndex);

    HashMap<String, Object> data = new HashMap<>();
    data.put("data", "ok");
    return data;
}

@PostMapping("mergeFragmentFile")
public Object mergeFragmentFile(@RequestParam String realFilename) throws IOException {

    System.out.println("-------开始合并文件");

    // 合并的文件
    RandomAccessFile raf = new RandomAccessFile("d:/Projects/practice/test-springboot/src/main/resources/file/" + realFilename, "rw");

    // 获取分片所在文件夹
    String md5 = DigestUtils.md5Hex(realFilename);
    System.out.println(realFilename);
    System.out.println(md5);
    File file = new File("d:/Projects/practice/test-springboot/src/main/resources/file/fragment/" + md5);
    File[] files = file.listFiles();
    int num = files.length;
    System.out.println(num);

    byte[] bytes = new byte[5 * 1024];

    // 合并分片
    for (int i = 0; i < num; i++) {
        File iFile = new File(file, String.valueOf(i));
        // 将每一个分片文件包装为缓冲流
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(iFile));
        int len = 0;
        // 将分片文件包装的流写入RandomAccessFile
        while ((len = bis.read(bytes)) != -1) {
            raf.write(bytes, 0, len);
        }
        bis.close();
    }

    // 删除分片所在文件夹的分片文件
    for (File tmpFile : files) {
        tmpFile.delete();
    }
    // 删除分片所在文件夹
    file.delete();

    raf.close();

    HashMap<String, Object> data = new HashMap<>();
    data.put("data", "ok");
    return data;
}

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

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

相关文章

zynq基于XDMA实现PCIE X8视频采集卡 提供工程源码和QT上位机程序和技术支持

目录 1、前言2、我已有的PCIE方案3、基于zynq架构的PCIE4、总体设计思路和方案视频输入通路视频输出通路PCIE输出上位机通路 5、vivado工程详解6、SDK 工程详解7、驱动安装8、QT上位机软件9、上板调试验证10、福利&#xff1a;工程代码的获取 1、前言 PCIE&#xff08;PCI Exp…

智能无人蜂群作战系统适应性进化模型仿真研究

源自&#xff1a;系统仿真学报 作者&#xff1a;李志强, 李元龙, 殷来祥, 马向平 摘 要 智能无人蜂群作战系统主要由有限行为能力的大规模作战个体组成&#xff0c;一般不具备应对复杂战场环境和作战对手变化的适应能力。采用遗传算法与增强学习相结合的方法探索构建基于个体…

Apache Flink (最新版本) 远程代码执行

路虽远&#xff0c;行则将至&#xff1b;事虽难&#xff0c;做则必成 Apache Flink < 1.9.1(最新版本) 远程代码执行 CVE-2020-17518 漏洞描述 近日,有安全研究员公开了一个Apache Flink的任意Jar包上传导致远程代码执行的漏洞. 漏洞影响 Apache Flink < 1.9.1(最新…

通过频谱规划软件摆脱频谱监测硬件限制

背景 随着无线通信技术的发展,电磁频谱被逐渐扩充&#xff0c;从几kHz到1THz的频段慢慢被各种技术填充与覆盖。在任意时刻任意地点&#xff0c;5G、WiFi、GNSS、广播电台、航空通信等&#xff0c;都离不开无线通信。 电磁频谱&#xff08;EMS&#xff09;被广泛用于生活中&am…

基于matlab使用均匀矩形阵列进行电子扫描

一、前言 本示例模拟定期扫描预定义监视区域的相控阵雷达。该单基地雷达使用900元件矩形阵列。介绍了根据规范推导雷达参数的步骤。合成接收到的脉冲后&#xff0c;进行检测和距离估计。最后&#xff0c;利用多普勒估计得到每个目标的速度。 二、雷达定义 首先&#xff0c;我们…

M301H-BYT代工-支持Hi3798 MV300H/MV300/MV310芯片-当贝纯净桌面-强刷卡刷固件包

M301H-BYT代工-支持Hi3798 MV300H&#xff0f;MV300&#xff0f;MV310芯片-当贝纯净桌面-强刷卡刷固件包 特点&#xff1a; 1、适用于对应型号的电视盒子刷机&#xff1b; 2、开放原厂固件屏蔽的市场安装和u盘安装apk&#xff1b; 3、修改dns&#xff0c;三网通用&#xff…

算法学习-图像的数据格式BGR

OpenCV学习——图像的BGR格式解读 1. opencv读取的图片数据格式2. BGR含义 1. opencv读取的图片数据格式 opencv读取的图片数据格式为numpy的nparray格式。 一张二维图片是由像素点构成&#xff0c;如下图所示&#xff1a; 其中行与列确定了像素点的位置&#xff0c;值确定了…

美团企业版:地利尚可,天时不足

配图来自Canva可画 近年来入局B端逐渐成为各家互联网大厂的必然选项&#xff0c;美团自然不甘心落于人后。 4月13日&#xff0c;美团企业版正式上线&#xff0c;面向企业客户推出一站式企业消费管理服务&#xff0c;覆盖团餐、差旅等场景&#xff0c;同时推出“企航计划”&am…

电脑视频删除了怎么恢复回来?很着急

案例分享&#xff1a;“电脑视频删除了怎么恢复回来&#xff1f;我是一名影楼的摄像师&#xff0c;我的主要工作就是拍摄婚礼视频&#xff0c;最近拍了一场婚礼视频&#xff0c;当时由于相机的内存不足&#xff0c;于是将宣传片等视频都导入进了电脑里面&#xff0c;清空摄像机…

自定义控件 (?/N) - 颜料 Paint

参考来源 一、颜色 1.1 直接设置颜色 1.1.1 setColor( ) public void setColor(ColorInt int color) paint.setColor(Color.RED) paint.setColor(Color.parseColor("#009688")) 1.1.2 setARGB( ) public void setARGB(int a, int r, int g, int b) paint.se…

多商户商城系统-v2.2.3版本发布

likeshop多商户商城系统-v2.2.3版本发布了&#xff01;主要更新内容如下 新增 1.用户端退出账号功能 优化 1.平台添加营业执照保存异常问题 2.平台端分销商品优化-只显示参与分销的商品 3.优化订单详情显示营销价格标签 4.平台交易设置增加默认值 5.种草社区评论调整&a…

如何下载安装驱动

1 打开浏览器 这里以Edge浏览器举例 第一步打开桌面上的Edge浏览器 如果您的桌面上没有 那么找到搜索栏 搜索Edge 然后打开 打开之后一般是这样 然后把我发送您的地址 驱动下载地址 https://t.lenovo.com.cn/yfeyfYyD &#xff08;这个网址只是一个例子&#xff09; 删除掉前…

MQ主流中间件

MQ主流中间件 目前&#xff0c;在消息中间件的领域中&#xff0c;主流的组件包括以下几种&#xff1a; Apache Kafka&#xff1a;一个分布式流处理平台&#xff0c;可以用于构建实时数据管道和流式应用程序。 RabbitMQ&#xff1a;一个实现了 AMQP&#xff08;高级消息队列协…

【Spring Security第一篇】初始Spring Security、表单认证、认证与授权

文章目录 一、初识Spring Security1. Spring Security简介2. 第一个Spring Security项目&#xff08;XML文件配置&#xff09;3. 第一个Spring Security项目&#xff08;自动配置&#xff09;4. 配置Security账户 二、表单认证1. Web 应用中基于密码的身份认证机制2. 默认表单认…

基于 Rainbond 的混合云管理解决方案

内容概要&#xff1a;文章探讨了混合云场景中的难点、要点&#xff0c;以及Rainbond平台在跨云平台的混合云管理方面的解决方案。包括通过通过统一控制台对多集群中的容器进行编排和管理&#xff0c;实现了对混合云中应用的一致性管理。文章还介绍了Rainbond平台在混合云环境下…

程序员的新电脑应该安装那些环境呢?

换新电脑了&#xff0c;那么作为一名程序员需要安装那些软件呢&#xff1f; 电脑系统版本&#xff1a;Windows11 注意&#xff1a;用户名一定要设置成英文的&#xff0c;否则后面会出现一定的问题&#xff01;&#xff01; 1、配置环境 &#xff08;1&#xff09;JDK环境 h…

HIEE300024R4 UAA326A04电流、电压、功率测量机电指示仪表的选用

​ HIEE300024R4 UAA326A04电流、电压、功率测量机电指示仪表的选用 电流、电压、功率测量机电指示仪表的选用 用于测量电流和电压的仪器类型如下 不 乐器 适用于 1个 PMMC&#xff08;永磁动圈&#xff09; 直流电流 2个 动铁式 交直流 3个 电测力计式 …

vcruntime140_1.dll丢失的解决方法

vcruntime140_1.dll是Microsoft Visual C Redistributable中的一个动态链接库&#xff08;DLL&#xff09;文件&#xff0c;是电脑Windows系统中重要的文件&#xff0c;丢失会造成很多软件报错无法运行。有不少小伙伴在打开ps&#xff0c;pr或者游戏的过程中都遇到过这个问题&a…

辅助驾驶功能开发-功能规范篇(16)-2-领航辅助系统NAP-巡航跟车基础功能

接上篇博文 2.3.2.巡航跟车基础功能 巡航跟车基础功能介绍辅助驾驶系统的车速设定,车间时距设定,纵向定速巡航、跟车加减速、起停,横向居中控制,弯道控制等逻辑。 前置条件: (1)NOA功能激活; 2.3.2.1.车速调节 1)激活时初始显示 中控屏设置界面有“融合限速设置”的开…

成功上岸国科大研究生!

Datawhale干货 作者&#xff1a;罗龙&#xff0c;江西财经大学&#xff0c;Datawhale优秀助教 中国科学院大学简介 中国科学院大学&#xff08;University of Chinese Academy of Sciences&#xff09;&#xff0c;简称“国科大”&#xff0c;是一所以研究生教育为主的科教融合…