第一次写分片上传,记录一下~
刚学Rust没多久,代码有点乱,勉强可以浅看一看
还没实现 续传,未来可能更新这篇文章
首先
确定临时文件目录
fn 分片_root_path<P: AsRef<Path>>( path: P)-> PathBuf {
Path::new("./public/分片").join(path)
}
确定trait(接口)
pub trait 分片 {
/**
#推入一个分片
*/
fn push_data(&self,piece_number:usize,piece_data:&[u8]);
/**
#合成文件
**return** : [`None`]:合成失败 , [`Some`] (文件,总分片数)
*/
fn make_file(&self)->Option<(File,usize)>;
/**
#删除所有分片
*/
fn delete_all_piece(self);
}
确定操作器
/**
建立以 id 命名的目录
分片文件名从 1 开始
0 文件名是最后合成的文件名
*/
pub struct 分片操作器(String);//.0 是id
确定构造器
///构建一个 [`分片操作器`]
pub struct 分片构造器;
impl 分片构造器 {//代码比较简单一起写在这里啦
pub fn new(id:String) -> 分片操作器 {
DirBuilder::new().create(分片_root_path(&id));
分片操作器(id)
}
}
一些操作用的函数 , 注意是函数哦~
impl 分片操作器{
pub fn path(&self) -> PathBuf {//获取路径,这是方法,图方便用的
分片_root_path(&self.0)
}
fn delete_all_piece(id:&String){//删除
fs::remove_dir_all(分片_root_path(id));
}
///piece_number 是 第piece_number个分片,用来确定文件名
fn create_piece(id:&String,piece_number:usize,data:&[u8]){//创建一个分片
let path_buf = 分片_root_path(id);
// DirBuilder::new().create(path_buf.as_path());//由分片构造器创建目录
let mut piece_file =
File::create(path_buf.join(piece_number.to_string()))
.unwrap();//上面必然存在目录,所以这个不用担心啦
piece_file.write(data);
}
fn make_file(id:&String) -> Option<(File,usize)>{//( 合成后的文件 , 总分片数 )
let path = 分片_root_path(id);
let mut list:Vec<usize> = Vec::new();
if let Ok(v) = read_dir(&path) {//这里比较乱,建议复制到IDE中去看
//大概功能就是 遍历 路径下的全部文件,把他们推入 list 中,用于有序合成文件
for v in v {
if let Ok(v) = v {
let file_name = v.file_name().into_string().unwrap();
let i = file_name.parse::<usize>().unwrap();
if i!=0{
(&mut list).push(i);
}
}
}
}else { //Err
return None;
}
list.sort();//排序
let mut file = File::create(&path.join("0"))
.unwrap();//上面有检查,所以不用担心这个啦
let mut buf = [0_u8;1024];//缓冲区
for v in &list {
let mut f = File::open(&path.join(v.to_string()))
.unwrap();//由上面找到的,所以必然存在,不用担心啦~
while let Ok(v) = f.read(&mut buf){
if v==0 {break;}
(&mut file).write(&buf[0..v]);
};
}
Some((file,list.len()))
}
}
实现 分片trait
impl 分片 for 分片操作器 {
fn push_data(&self, piece_number: usize, piece_data: &[u8]) {
//这里其实我想去掉 piece_number ,通过扫描目录下的文件,来确定文件名的,但是又感觉限制操作了
分片操作器::create_piece(&self.0,piece_number,piece_data)
}
fn make_file(&self) -> Option<(File, usize)> {
分片操作器::make_file(&self.0)
}
fn delete_all_piece(self) {
分片操作器::delete_all_piece(&self.0);
}
}
最后是 业务 逻辑
#[derive(FromForm)]
pub struct PieceForm<'a> {
id:String,
piece_number:String,// 0:完成分片,可以合成
file_data:TempFile<'a>
}
#[post("/file",data = "<file>")]
pub async fn post_file_cs(mut file: Form<PieceForm<'_>>) -> std::io::Result<String> {
println!("{}",&file.id);
println!("{}",&file.piece_number);
let piece_number = file.piece_number.clone();
let piece = 分片构造器::new(file.id.clone());
if file.piece_number.eq("0") {
if let Some((file,i))= piece.make_file() {
println!("文件合成完成!");
println!("总分片数:{}",i);
println!("文件:{:#?}",file);
return Ok(format!("Ok:合成成功,总片数:[{}]",i));
}
return Ok(format!("Err:合成失败"))
}
file.file_data.persist_to(piece
.path().join(&piece_number)).await?;//它居然会直接建立文件...让我的函数显得很没用耶
Ok(piece_number)
}
这是前端代码 (Vue)
<template><div>
<input type="file" ref="file" >
<n-button @click="请求">请求</n-button>
</div></template>
<script lang="ts" setup>
import { ref , reactive , computed , watchEffect , defineProps , withDefaults , defineEmits , defineExpose , useSlots , useAttrs, onMounted} from 'vue';
import { useStore } from "vuex";
import { useRouter, useRoute } from "vue-router";
import axios from "axios"
import {Md5} from "ts-md5"
const router = useRouter();
const file: File | any = ref({});
async function 请求() {
let f: File | any = file.value.files[0];
console.log(f);
if(f==undefined||f==null){
return;
}
const piece_size = 1024*256;//每片大小 256KiB
// const piece_size = 10;//测试用的
const len = f.size;//文件大小
const name = f.name;//文件名
const piece_number = Math.ceil(len/piece_size); //片数
const id =name + "+" + new Md5().appendAsciiStr(
await f.text()).end(false) as string;
console.log("分片总数:"+piece_number);
console.log("文件名:"+f.name);
for(let i = 1; i <= piece_number; i++){
if(i==piece_number){ //最后一片上传
const form = new FormData();
form.set("id",id)
form.set("piece_number",piece_number.toString());
form.set("file_data",f
.slice((piece_number-1)*piece_size
,(piece_number-1)*piece_size+len%piece_size));
const a = await axios.post("/api/rust/cs/file",form);
console.log(a);
break;
}
const form = new FormData();
form.set("id",id)
form.set("piece_number",i.toString());
form.set("file_data",f.slice((i-1)*piece_size,i*piece_size));
const a = await axios.post("/api/rust/cs/file",form);
console.log(a);
}
//告诉后端完成传输,允许合成文件
const form = new FormData();
form.set("id",id)
form.set("piece_number","0");
form.set("file_data",f.slice(0,0));
const a = await axios.post("/api/rust/cs/file",form);
console.log(a);
}
</script>
Rust 写起来就是得劲!
以上代码并没有 检查逻辑问题 和 完善代码结构(前端代码可以证明,哈哈),大家浅看就好~