【Java】 Springboot+Vue 大文件断点续传
同事在重构老系统的项目时用到了这种大文件上传
第一篇文章是简书的这个:
https://www.jianshu.com/p/b59d7dee15a6
是夏大佬写的vue-uploader组件:
https://www.cnblogs.com/xiahj/p/15950975.html
然后晚上看完才发现,没有后台接口...
然后我找了下网上的资料也不多,然后参考的是这位来实现:
https://github.com/LuoLiangDSGA/spring-learning/tree/bc60e349b4c573fb624230d068f7bb66a9e64736/boot-uploader
我的Springboot接口实现:
1、Chuck分块对象实体:
package cn.cloud9.server.struct.file.dto; import com.alibaba.fastjson.annotation.JSONField;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.data.annotation.Transient;
import org.springframework.web.multipart.MultipartFile; import java.io.Serializable; /**
* @author OnCloud9
* @description
* @project tt-server
* @date 2022年11月18日 下午 11:02
*/ @Data
@EqualsAndHashCode(callSuper = true)
@TableName("chuck")
public class Chuck extends ChuckFile implements Serializable { /**
* 当前文件块,从1开始
*/
@TableField("CHUNK_NUMBER")
private Integer chunkNumber;
/**
* 分块大小
*/
@TableField("CHUNK_SIZE")
private Long chunkSize;
/**
* 当前分块大小
*/
@TableField("CURRENT_CHUNK_SIZE")
private Long currentChunkSize; /**
* 相对路径
*/
@TableField("RELATIVE_PATH")
private String relativePath;
/**
* 总块数
*/
@TableField("TOTAL_CHUNKS")
private Integer totalChunks; /**
* form表单的file对象,为了不让Fastjson序列化,注解设置false
*/
@Transient
@JSONField(serialize = false)
@TableField(exist = false)
private MultipartFile file;
}
2、分块文件实体:
package cn.cloud9.server.struct.file.dto; import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import org.springframework.data.annotation.Id; import java.io.Serializable; /**
* @author OnCloud9
* @description
* @project tt-server
* @date 2022年11月18日 下午 11:00
*/
@Data
@TableName("chuck_file")
public class ChuckFile implements Serializable {
@TableId(value = "ID", type = IdType.AUTO)
protected Long id; @TableField("FILENAME")
protected String filename; @TableField("IDENTIFIER")
protected String identifier; @TableField("TOTAL_SIZE")
protected Long totalSize; @TableField("TYPE")
protected String type; @TableField("LOCATION")
protected String location;
}
3、保存分块信息,判断分块是否上传了
package cn.cloud9.server.struct.file.service.impl; import cn.cloud9.server.struct.file.dto.Chuck;
import cn.cloud9.server.struct.file.mapper.ChuckMapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; import java.util.List; /**
* @author OnCloud9
* @description
* @project tt-server
* @date 2022年11月18日 下午 11:08
*/
@Slf4j
@Service
public class ChuckService extends ServiceImpl<ChuckMapper, Chuck> { /**
* 保存分块信息
* @param chuck
*/
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public Chuck saveChuck(Chuck chuck) {
final int insert = baseMapper.insert(chuck);
return 1 == insert ? chuck : null;
} /**
* 判断该分块是否上传过
* @param chuck
* @return
*/
public boolean checkHasChucked(Chuck chuck) {
final List<Chuck> chucks = lambdaQuery()
.eq(Chuck::getIdentifier, chuck.getIdentifier())
.eq(Chuck::getChunkNumber, chuck.getChunkNumber())
.list();
return CollectionUtils.isNotEmpty(chucks);
}
}
4、分块文件服务Bean就两个作用:合并分块,写入合并后的文件信息:
这里原封不动照抄作者的逻辑,就是排序作者写错了用倒序,害我检查半天哪没写对,文件一直合成是坏的
package cn.cloud9.server.struct.file.service.impl; import cn.cloud9.server.struct.file.dto.ChuckFile;
import cn.cloud9.server.struct.file.mapper.ChuckFileMapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption; /**
* @author OnCloud9
* @description
* @project tt-server
* @date 2022年11月18日 下午 11:08
*/
@Slf4j
@Service
public class ChuckFileService extends ServiceImpl<ChuckFileMapper, ChuckFile> { public void mergeChuckFile(String storagePath, ChuckFile chuckFile) {
try {
final String finalFile = storagePath + File.separator + chuckFile.getFilename(); Files.createFile(Paths.get(finalFile));
Files.list(Paths.get(storagePath))
/* 1、过滤非合并文件 */
.filter(path -> path.getFileName().toString().contains("-"))
/* 2、升序排序 0 - 1 - 2 。。。 */
.sorted((o1, o2) -> {
String p1 = o1.getFileName().toString();
String p2 = o2.getFileName().toString();
int i1 = p1.lastIndexOf("-") + 1;
int i2 = p2.lastIndexOf("-") + 1;
return Integer.valueOf(p1.substring(i1)).compareTo(Integer.valueOf(p2.substring(i2)));
})
/* 3、合并文件 */
.forEach(path -> {
try {
/* 以追加的形式写入文件 */
Files.write(Paths.get(finalFile), Files.readAllBytes(path), StandardOpenOption.APPEND);
/* 合并后删除该块 */
Files.delete(path);
} catch (IOException e) {
e.printStackTrace();
}
});
} catch (Exception e) {
log.error("{}合并写入异常:{}", chuckFile.getFilename(), e.getMessage());
}
} public void addChuckFile(ChuckFile chuckFile) {
baseMapper.insert(chuckFile);
}
}
5、Controller的三个接口
这里Chuck接口的逻辑就没封装到服务了
package cn.cloud9.server.struct.file.controller; import cn.cloud9.server.struct.controller.BaseController;
import cn.cloud9.server.struct.file.FileProperty;
import cn.cloud9.server.struct.file.FileUtil;
import cn.cloud9.server.struct.file.dto.Chuck;
import cn.cloud9.server.struct.file.dto.ChuckFile;
import cn.cloud9.server.struct.file.service.impl.ChuckFileService;
import cn.cloud9.server.struct.file.service.impl.ChuckService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.File; /**
* @author OnCloud9
* @description 大型文件上传
* @project tt-server
* @date 2022年11月18日 下午 10:58
*/
@Slf4j
@RestController
@RequestMapping("/chuck-file")
public class ChuckFileController extends BaseController { private static final String CHUCK_DIR = "chuck"; @Resource
private FileProperty fileProperty; @Resource
private ChuckFileService chuckFileService; @Resource
private ChuckService chuckService; /**
* 上传分块文件
* @param chuck 分块文件
* @return 响应结果
*/
@PostMapping(value = "/chuck", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public Object uploadFileChuck(@ModelAttribute Chuck chuck) {
final MultipartFile mf = chuck.getFile();
log.info("收到文件块 文件块名:{} 块编号:{}", mf.getOriginalFilename(), chuck.getChunkNumber());
try { /* 1、准备存储位置 根目录 + 分块目录 + 无类型文件名的目录 */
final String pureName = FileUtil.getFileNameWithoutTypeSuffix(mf.getOriginalFilename());
String storagePath = fileProperty.getBaseDirectory() + File.separator + CHUCK_DIR + File.separator + pureName;
final File storePath = new File(storagePath);
if (!storePath.exists()) storePath.mkdirs(); /* 2、准备分块文件的规范名称 [无类型文件名 -分块号] */
String chuckFilename = pureName + "-" + chuck.getChunkNumber(); /* 3、向存储位置写入文件 */
final File targetFile = new File(storePath, chuckFilename);
mf.transferTo(targetFile);
log.debug("文件 {} 写入成功, uuid:{}", chuck.getFilename(), chuck.getIdentifier()); chuck = chuckService.saveChuck(chuck);
} catch (Exception e) {
log.error("文件上传异常:{}, {}", chuck.getFilename(), e.getMessage());
return e.getMessage();
}
return chuck;
} /**
* 检查该分块文件是否上传了
* @param chuck 分块文件
* @return 304 | 分块文件 has-chucked
*/
@GetMapping("/chuck")
public Object checkHasChucked(@ModelAttribute Chuck chuck) {
final boolean hasChucked = chuckService.checkHasChucked(chuck);
if (hasChucked) response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
return chuck;
} /**
* 文件合并
* @param chuckFile 分块文件合并信息
*/
@PostMapping("/merge")
public void mergeChuckFile(@ModelAttribute ChuckFile chuckFile) {
/* 获取存储位置 */
final String pureName = FileUtil.getFileNameWithoutTypeSuffix(chuckFile.getFilename());
String storagePath = fileProperty.getBaseDirectory() + File.separator + CHUCK_DIR + File.separator + pureName;
chuckFileService.mergeChuckFile(storagePath, chuckFile); chuckFile.setLocation(storagePath);
chuckFileService.addChuckFile(chuckFile);
}
}
6、根目录的配置bean:
package cn.cloud9.server.struct.file; import lombok.Data;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration; /**
* @author OnCloud9
* @description
* @project tt-server
* @date 2022年11月15日 下午 11:34
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "file")
public class FileProperty { /* 文件基础目录位置 */
private String baseDirectory;
}
配置文件写法:
file:
base-directory: F:\\tt-file
文件工具类代码:
package cn.cloud9.server.struct.file; import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.tika.Tika;
import sun.misc.BASE64Encoder; import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.Month; /**
* @author OnCloud9
* @description
* @project tt-server
* @date 2022年11月15日 下午 11:27
*/
@Slf4j
public class FileUtil { /**
* 根据当前年月创建目录
* @return yyyy_mm
*/
public static String createDirPathByYearMonth() {
final LocalDateTime now = LocalDateTime.now();
final int year = now.getYear();
final Month month = now.getMonth();
final int monthValue = month.getValue();
return year + "_" + monthValue;
} /**
* 获取文件类型后缀
* @param filename 文件名称
* @return 文件类型
*/
public static String getFileTypeSuffix(String filename) {
final int pointIndex = filename.lastIndexOf(".");
final String suffix = filename.substring(pointIndex + 1);
return StringUtils.isBlank(suffix) ? "" : suffix;
} /**
* 获取不带类型后缀的文件名
* @param filename 文件名称
* @return 不带类型后缀的文件名
*/
public static String getFileNameWithoutTypeSuffix(String filename) {
final int pointIndex = filename.lastIndexOf(".");
final String pureName = filename.substring(0, pointIndex);
return StringUtils.isBlank(pureName) ? "" : pureName;
} /**
* 设置文件下载的响应信息
* @param response 响应对象
* @param file 文件
* @param originFilename 源文件名
*/
public static void setDownloadResponseInfo(HttpServletResponse response, File file, String originFilename) {
try {
response.setCharacterEncoding("UTF-8");
/* 文件名 */
final String fileName = StringUtils.isBlank(originFilename) ? file.getName() : originFilename; /* 获取要下载的文件类型, 设置文件类型声明 */
String mimeType = new Tika().detect(file);
response.setContentType(mimeType); /* 设置响应头,告诉该文件用于下载而非展示 attachment;filename 类型:附件,文件名称 */
final String header = response.getHeader("User-Agent");
if (StringUtils.isNotBlank(header) && header.contains("Firefox")){
/* 对火狐浏览器单独设置 */
response.setHeader("Content-Disposition","attachment;filename==?UTF-8?B?"+ new BASE64Encoder().encode(fileName.getBytes(StandardCharsets.UTF_8))+"?=");
}
else response.setHeader("Content-Disposition","attachment;filename="+ URLEncoder.encode(fileName,"UTF-8"));
} catch (Exception e) {
log.info("设置响应信息异常:{}", e.getMessage());
}
} }
获取MimeType类型需要这个Tika组件支持:
<!-- https://mvnrepository.com/artifact/org.apache.tika/tika-core -->
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-core</artifactId>
<version>2.6.0</version>
</dependency>
Web接口组件:
直接克隆作者的项目
1、把接口改成你写的接口地址 一个chuck 一个merge (chuck会被组件用Post和Get区分请求好像,post就传分块,get检查分块上传了没有)
<template>
<uploader :options="options" :file-status-text="statusText" class="uploader-example" ref="uploader" @file-complete="fileComplete" @complete="complete"></uploader>
</template> <script>
import axios from 'axios'
import qs from 'qs'
export default {
data () {
return {
options: {
target: 'http://localhost:8080/chuck-file/chuck', // '//jsonplaceholder.typicode.com/posts/',
testChunks: false
},
attrs: {
accept: 'image/*'
},
statusText: {
success: '成功了',
error: '出错了',
uploading: '上传中',
paused: '暂停中',
waiting: '等待中'
}
}
},
methods: {
complete () {
console.log('complete', arguments)
},
fileComplete () {
console.log('file complete', arguments)
const file = arguments[0].file
axios.post('http://localhost:8080/chuck-file/merge', qs.stringify({
filename: file.name,
identifier: arguments[0].uniqueIdentifier,
totalSize: file.size,
type: file.type
})).then(function (response) {
console.log(response)
}).catch(function (error) {
console.log(error)
})
}
},
mounted () {
this.$nextTick(() => {
window.uploader = this.$refs.uploader.uploader
})
}
}
</script> <style>
.uploader-example {
width: 880px;
padding: 15px;
margin: 40px auto 0;
font-size: 12px;
box-shadow: 0 0 10px rgba(0, 0, 0, .4);
}
.uploader-example .uploader-btn {
margin-right: 4px;
}
.uploader-example .uploader-list {
max-height: 440px;
overflow: auto;
overflow-x: hidden;
overflow-y: auto;
}
</style>
2、区分后台的端口:
在config / index.js 里面重新配置web的端口:
8080 改成 8081
// see http://vuejs-templates.github.io/webpack for documentation.
var path = require('path') module.exports = {
build: {
env: require('./prod.env'),
index: path.resolve(__dirname, '../dist/index.html'),
assetsRoot: path.resolve(__dirname, '../dist'),
assetsSubDirectory: '',
assetsPublicPath: './',
productionSourceMap: true,
// Gzip off by default as many popular static hosts such as
// Surge or Netlify already gzip all static assets for you.
// Before setting to `true`, make sure to:
// npm install --save-dev compression-webpack-plugin
productionGzip: false,
productionGzipExtensions: ['js', 'css'],
// Run the build command with an extra argument to
// View the bundle analyzer report after build finishes:
// `npm run build --report`
// Set to `true` or `false` to always turn it on or off
bundleAnalyzerReport: process.env.npm_config_report
},
dev: {
env: require('./dev.env'),
port: 8081,
autoOpenBrowser: true,
assetsSubDirectory: '',
assetsPublicPath: '/',
proxyTable: {},
// CSS Sourcemaps off by default because relative paths are "buggy"
// with this option, according to the CSS-Loader README
// (https://github.com/webpack/css-loader#sourcemaps)
// In our experience, they generally work as expected,
// just be aware of this issue when enabling this option.
cssSourceMap: false
}
}
这里可以看到就两个依赖:
"dependencies": {
"axios": "^1.1.3",
"simple-uploader.js": "^0.5.6"
},
分块文件表记录:
mysql> SELECT * FROM chuck_file;
+----+------------------------------------------+----------------------------------------------+------------+--------------------+--------------------------------------------------------+
| ID | FILENAME | IDENTIFIER | TOTAL_SIZE | TYPE | LOCATION |
+----+------------------------------------------+----------------------------------------------+------------+--------------------+--------------------------------------------------------+
| 1 | charles中文破解版.rar | 135963354-charlesrar | 135963354 | | F:\\tt-file\chuck\charles中文破解版 |
| 2 | charles中文破解版.rar | 135963354-charlesrar | 135963354 | | F:\\tt-file\chuck\charles中文破解版 |
| 3 | charles中文破解版.rar | 135963354-charlesrar | 135963354 | | F:\\tt-file\chuck\charles中文破解版 |
| 4 | charles中文破解版.rar | 135963354-charlesrar | 135963354 | | F:\\tt-file\chuck\charles中文破解版 |
| 5 | charles中文破解版.rar | 135963354-charlesrar | 135963354 | | F:\\tt-file\chuck\charles中文破解版 |
| 6 | charles中文破解版.rar | 135963354-charlesrar | 135963354 | | F:\\tt-file\chuck\charles中文破解版 |
| 7 | mysql-8.0.29-1.el8.x86_64.rpm-bundle.tar | 793722880-mysql-8029-1el8x86_64rpm-bundletar | 793722880 | application/x-tar | F:\\tt-file\chuck\mysql-8.0.29-1.el8.x86_64.rpm-bundle |
| 8 | phoenix-hbase-2.4-5.1.2-bin.tar.gz | 207440936-phoenix-hbase-24-512-bintargz | 207440936 | application/x-gzip | F:\\tt-file\chuck\phoenix-hbase-2.4-5.1.2-bin.tar |
+----+------------------------------------------+----------------------------------------------+------------+--------------------+--------------------------------------------------------+
8 rows in set (0.02 sec)
表结构:
CREATE TABLE `chuck_file` (
`ID` int NOT NULL AUTO_INCREMENT,
`FILENAME` varchar(128) COLLATE utf8mb4_general_ci DEFAULT NULL,
`IDENTIFIER` varchar(128) COLLATE utf8mb4_general_ci DEFAULT NULL,
`TOTAL_SIZE` bigint DEFAULT NULL,
`TYPE` varchar(24) COLLATE utf8mb4_general_ci DEFAULT NULL,
`LOCATION` varchar(128) COLLATE utf8mb4_general_ci DEFAULT NULL,
PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE `chuck` (
`ID` int NOT NULL AUTO_INCREMENT,
`FILENAME` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`IDENTIFIER` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`TOTAL_SIZE` bigint DEFAULT NULL,
`TYPE` varchar(24) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`LOCATION` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`CHUNK_NUMBER` int DEFAULT NULL,
`CHUNK_SIZE` int DEFAULT NULL,
`CURRENT_CHUNK_SIZE` int DEFAULT NULL,
`RELATIVE_PATH` varchar(128) COLLATE utf8mb4_general_ci DEFAULT NULL,
`TOTAL_CHUNKS` int DEFAULT NULL,
PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=1734 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
最后几点事项:
1、分块文件的命名规则最好是严谨点,影响合并操作,一般中文无特殊符号文件名不会有影响
2、不建议立即删除分块,可以先对合并后的文件进行校验,如果不存在或者大小不一致,可以继续拿分块重新合并
【Java】 Springboot+Vue 大文件断点续传的更多相关文章
- vue+大文件断点续传
根据部门的业务需求,需要在网络状态不良的情况下上传很大的文件(1G+).其中会遇到的问题:1,文件过大,超出服务端的请求大小限制:2,请求时间过长,请求超时:3,传输中断,必须重新上传导致前功尽弃.解 ...
- iOS开发之网络编程--使用NSURLConnection实现大文件断点续传下载+使用输出流代替文件句柄
前言:本篇讲解,在前篇iOS开发之网络编程--使用NSURLConnection实现大文件断点续传下载的基础上,使用输出流代替文件句柄实现大文件断点续传. 在实际开发中,输入输出流用的比较少,但 ...
- vue大文件上传控件选哪个好?
需求: 项目要支持大文件上传功能,经过讨论,初步将文件上传大小控制在20G内,因此自己需要在项目中进行文件上传部分的调整和配置,自己将大小都以20G来进行限制. PC端全平台支持,要求支持Window ...
- java+上传大文件
在Web应用系统开发中,文件上传和下载功能是非常常用的功能,今天来讲一下JavaWeb中的文件上传和下载功能的实现. 先说下要求: PC端全平台支持,要求支持Windows,Mac,Linux 支持所 ...
- HTML5 大文件断点续传完整思路整理
需求: 支持大文件批量上传(20G)和下载,同时需要保证上传期间用户电脑不出现卡死等体验: 内网百兆网络上传速度为12MB/S 服务器内存占用低 支持文件夹上传,文件夹中的文件数量达到1万个以上,且包 ...
- B/S大文件断点续传
一. 功能性需求与非功能性需求 要求操作便利,一次选择多个文件和文件夹进行上传:支持PC端全平台操作系统,Windows,Linux,Mac 支持文件和文件夹的批量下载,断点续传.刷新页面后继续传输. ...
- Java快速读取大文件
Java快速读取大文件 最近公司服务器监控系统需要做一个东西来分析Java应用程序的日志. 第一步探索: 首先我想到的是使用RandomAccessFile,因为他可以很方便的去获取和设置文件指针,下 ...
- php实现大文件断点续传下载实例
php实现大文件断点续传下载实例,看完你就知道超过100M以上的大文件如何断点传输了,这个功能还是比较经典实用的,毕竟大文件上传功能经常用得到. require_once('download.clas ...
- java+大文件断点续传
用JAVA实现大文件上传及显示进度信息 ---解析HTTP MultiPart协议 (本文提供全部源码下载,请访问 https://github.com/1269085759/up6-jsp-mysq ...
- java大文件断点续传
java两台服务器之间,大文件上传(续传),采用了Socket通信机制以及JavaIO流两个技术点,具体思路如下: 实现思路:1.服:利用ServerSocket搭建服务器,开启相应端口,进行长连接操 ...
随机推荐
- Grafana监控系统的构建与实践
本文深入探讨了Grafana的核心技术.数据源集成.仪表盘与可视化构建以及监控与告警配置,旨在为专业从业者提供全面的Grafana技术指南. 关注[TechLeadCloud],分享互联网架构.云服务 ...
- 2024-06-05:用go语言,给定三个正整数 n、x 和 y, 描述一个城市中由 n 个房屋和 n 条街道连接的情况。 城市中存在一条额外的街道连接房屋 x 和房屋 y。 需要计算对于每个街道数(
2024-06-05:用go语言,给定三个正整数 n.x 和 y, 描述一个城市中由 n 个房屋和 n 条街道连接的情况. 城市中存在一条额外的街道连接房屋 x 和房屋 y. 需要计算对于每个街道数( ...
- Vuex 4与状态管理实战指南
title: Vuex 4与状态管理实战指南 date: 2024/6/6 updated: 2024/6/6 excerpt: 这篇文章介绍了使用Vuex进行Vue应用状态管理的最佳实践,包括为何需 ...
- PowerShell 遇到 .ps1,因为在此系统上禁止运行脚本
PowerShell 遇到 .ps1,因为在此系统上禁止运行脚本 解决方法: 以管理员身份打开PowerShell: 查看当前的执行策略: Get-ExecutionPolicy * `Restric ...
- (编程语言界的丐帮 C#).NET Framework 读取Excel到DataTable
(编程语言界的丐帮 C#).NET Framework 读取Excel到DataTable 生成DataTable到Excel,支持 2007 .xlsx,2003 .xls. nuget 引用 NP ...
- 打开TLS 1.1和1.2而不影响其他协议
打开TLS 1.1和1.2而不影响其他协议 System.Net.ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls11 ...
- flutter 环境搭配 (一)
首先下载flutter SDK Flutter中文网 官网 (p2hp.com 选择下载 SDK 解压后 ,添加到环境变量中. 配置国内镜像, PUB_HOSTED_URL=https://pub.f ...
- java8 lambda Predicate示例
import java.util.Arrays; import java.util.List; import java.util.function.Predicate; public class Pr ...
- Nivdia向量数据库图检索最新标杆——CAGRA
本文连接:https://wanger-sjtu.github.io/CARGA/ CAGRA 是 N社在RAFT项目中 最新的 ANN 向量索引.这是一种高性能的. GPU 加速的.基于图的方法,尤 ...
- k8s中的亲和性、污点与容忍(调度到不同的宿主机)
本文章并未经过严格实践,如有错误,辛苦指出. 背景 服务需要多副本,来保证高可靠.多活. 那么问题来了,假如这些副本都在同一个宿主机上,或者同一个交换机下-宿主机.交换机其中一项坏掉了,那多副本还有什 ...