Skip to content

Commit 18178d5

Browse files
committed
✨ 支持百度网盘备份鉴权
1 parent c4d4de4 commit 18178d5

File tree

22 files changed

+507
-43
lines changed

22 files changed

+507
-43
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"build": "webpack --mode production --config ./webpack/webpack.prod.ts && concurrently \"npm run build:linter\" \"npm run build:no-split\"",
1212
"build:linter": "webpack --mode production --config ./webpack/webpack.linter.ts",
1313
"build:no-split": "webpack --mode production --config ./webpack/webpack.no.split.ts",
14-
"dev:linter": "webpack --mode development --config ./webpack/webpack.linter.ts --watch",
14+
"dev:linter": "webpack --mode development --config ./webpack/webpack.linter.dev.ts",
1515
"pack": "node ./build/pack.js"
1616
},
1717
"dependencies": {

pkg/filesystem/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44

55
- zip
66
- webdav
7+
- 百度网盘

pkg/filesystem/auth.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/* eslint-disable import/prefer-default-export */
2+
import Cache from "@App/app/cache";
3+
import { ExtServer } from "@App/app/const";
4+
import { api } from "@App/pkg/axios";
5+
6+
type NetDiskType = "baidu";
7+
8+
export function GetNetDiskToken(netDiskType: NetDiskType): Promise<{
9+
code: number;
10+
msg: string;
11+
data: { accessToken: string };
12+
}> {
13+
return api
14+
.get(`/auth/net-disk/token?netDiskType=${netDiskType}`)
15+
.then((resp) => {
16+
return resp.data;
17+
});
18+
}
19+
20+
export function NetDisk(netDiskType: NetDiskType) {
21+
return new Promise<void>((resolve) => {
22+
const loginWindow = window.open(
23+
`${ExtServer}api/v1/auth/net-disk?netDiskType=${netDiskType}`
24+
);
25+
const t = setInterval(() => {
26+
try {
27+
if (loginWindow!.closed) {
28+
clearInterval(t);
29+
resolve();
30+
}
31+
} catch (e) {
32+
clearInterval(t);
33+
resolve();
34+
}
35+
}, 1000);
36+
});
37+
}
38+
39+
export async function AuthVerify(netDiskType: NetDiskType, refresh?: boolean) {
40+
if (!refresh) {
41+
const data = Cache.getInstance().get(`netDiskToken:${netDiskType}`);
42+
// 大于一小时进行刷新
43+
if (data && Date.now() - data.time < 3600000) {
44+
return data.token;
45+
}
46+
}
47+
// 调用API查看是否已经验证过,否则进行重定向
48+
let token = await GetNetDiskToken(netDiskType);
49+
if (token.code !== 0) {
50+
// 申请
51+
await NetDisk(netDiskType);
52+
token = await GetNetDiskToken(netDiskType);
53+
}
54+
if (token.code !== 0) {
55+
return Promise.reject(new Error(token.msg));
56+
}
57+
Cache.getInstance().set(`netDiskToken:${netDiskType}`, {
58+
token: token.data.accessToken,
59+
time: Date.now(),
60+
});
61+
return Promise.resolve(token.data);
62+
}

pkg/filesystem/baidu/baidu.ts

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/* eslint-disable no-unused-vars */
2+
import { AuthVerify } from "../auth";
3+
import FileSystem, { File, FileReader, FileWriter } from "../filesystem";
4+
import { BaiduFileReader, BaiduFileWriter } from "./rw";
5+
6+
export default class BaiduFileSystem implements FileSystem {
7+
accessToken?: string;
8+
9+
path: string;
10+
11+
constructor(path?: string, accessToken?: string) {
12+
this.path = path || "/apps";
13+
this.accessToken = accessToken;
14+
}
15+
16+
async verify(): Promise<void> {
17+
const token = await AuthVerify("baidu");
18+
this.accessToken = token.accessToken;
19+
return Promise.resolve();
20+
}
21+
22+
open(file: File): Promise<FileReader> {
23+
// 获取fsid
24+
return Promise.resolve(new BaiduFileReader(this, file));
25+
}
26+
27+
openDir(path: string): Promise<FileSystem> {
28+
return Promise.resolve(
29+
new BaiduFileSystem(`${this.path}/${path}`, this.accessToken)
30+
);
31+
}
32+
33+
create(path: string): Promise<FileWriter> {
34+
return Promise.resolve(new BaiduFileWriter(this, `${this.path}/${path}`));
35+
}
36+
37+
createDir(dir: string): Promise<void> {
38+
dir = dir ? `${this.path}/${dir}` : this.path;
39+
const urlencoded = new URLSearchParams();
40+
urlencoded.append("path", dir);
41+
urlencoded.append("size", "0");
42+
urlencoded.append("isdir", "1");
43+
urlencoded.append("rtype", "3");
44+
const myHeaders = new Headers();
45+
myHeaders.append("Content-Type", "application/x-www-form-urlencoded");
46+
return this.request(
47+
`https://pan.baidu.com/rest/2.0/xpan/file?method=create&access_token=${this.accessToken}`,
48+
{
49+
method: "POST",
50+
headers: myHeaders,
51+
body: urlencoded,
52+
redirect: "follow",
53+
}
54+
).then((data) => {
55+
if (data.errno) {
56+
throw new Error(data);
57+
}
58+
return Promise.resolve();
59+
});
60+
}
61+
62+
// eslint-disable-next-line no-undef
63+
request(url: string, config?: RequestInit) {
64+
return fetch(url, config)
65+
.then((data) => data.json())
66+
.then(async (data) => {
67+
if (data.errno === 111) {
68+
await this.verify();
69+
return fetch(url, config)
70+
.then((data2) => data2.json())
71+
.then((data2) => {
72+
if (data2.errno === 111) {
73+
throw new Error(data2);
74+
}
75+
return data2;
76+
});
77+
}
78+
return data;
79+
});
80+
}
81+
82+
delete(path: string): Promise<void> {
83+
throw new Error("Delete Method not implemented.");
84+
}
85+
86+
list(path?: string | undefined): Promise<File[]> {
87+
return this.request(
88+
`https://pan.baidu.com/rest/2.0/xpan/file?method=list&dir=${encodeURIComponent(
89+
`${this.path}${path ? `/${path}` : ""}`
90+
)}&order=time&access_token=${this.accessToken}`
91+
).then((data) => {
92+
// 创建文件夹
93+
if (data.errno) {
94+
if (data.errno === -9) {
95+
this.createDir(path || "");
96+
return [];
97+
}
98+
throw new Error(data);
99+
}
100+
const list: File[] = [];
101+
data.list.forEach((val: any) => {
102+
list.push({
103+
fsid: val.fs_id,
104+
name: val.server_filename,
105+
path: val.path.substring(0, val.path.length - val.server_filename),
106+
size: val.size,
107+
digest: val.md5,
108+
createtime: val.server_ctime * 1000,
109+
updatetime: val.server_mtime * 1000,
110+
});
111+
});
112+
return list;
113+
});
114+
}
115+
}

pkg/filesystem/baidu/rw.ts

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/* eslint-disable max-classes-per-file */
2+
/* eslint-disable import/prefer-default-export */
3+
import { calculateMd5 } from "@App/pkg/utils/utils";
4+
import { MD5 } from "crypto-js";
5+
import { File, FileReader, FileWriter } from "../filesystem";
6+
import BaiduFileSystem from "./baidu";
7+
8+
export class BaiduFileReader implements FileReader {
9+
file: File;
10+
11+
fs: BaiduFileSystem;
12+
13+
constructor(fs: BaiduFileSystem, file: File) {
14+
this.fs = fs;
15+
this.file = file;
16+
}
17+
18+
async read(type?: "string" | "blob"): Promise<string | Blob> {
19+
switch (type) {
20+
case "string":
21+
return this.client.getFileContents(this.path, {
22+
format: "text",
23+
}) as Promise<string>;
24+
default: {
25+
const resp = (await this.client.getFileContents(this.path, {
26+
format: "binary",
27+
})) as ArrayBuffer;
28+
return Promise.resolve(new Blob([resp]));
29+
}
30+
}
31+
}
32+
}
33+
34+
export class BaiduFileWriter implements FileWriter {
35+
path: string;
36+
37+
fs: BaiduFileSystem;
38+
39+
constructor(fs: BaiduFileSystem, path: string) {
40+
this.fs = fs;
41+
this.path = path;
42+
}
43+
44+
size(content: string | Blob) {
45+
if (content instanceof Blob) {
46+
return content.size;
47+
}
48+
return content.length;
49+
}
50+
51+
async md5(content: string | Blob) {
52+
if (content instanceof Blob) {
53+
return calculateMd5(content);
54+
}
55+
return MD5(content).toString();
56+
}
57+
58+
async write(content: string | Blob): Promise<void> {
59+
// 预上传获取id
60+
const size = this.size(content).toString();
61+
const md5 = await this.md5(content);
62+
const blockList: string[] = [md5];
63+
let urlencoded = new URLSearchParams();
64+
urlencoded.append("path", this.path);
65+
urlencoded.append("size", size);
66+
urlencoded.append("isdir", "0");
67+
urlencoded.append("autoinit", "1");
68+
urlencoded.append("rtype", "3");
69+
urlencoded.append("block_list", JSON.stringify(blockList));
70+
const myHeaders = new Headers();
71+
myHeaders.append("Content-Type", "application/x-www-form-urlencoded");
72+
const uploadid = await this.fs
73+
.request(
74+
`http://pan.baidu.com/rest/2.0/xpan/file?method=precreate&access_token=${this.fs.accessToken}`,
75+
{
76+
method: "POST",
77+
headers: myHeaders,
78+
body: urlencoded,
79+
}
80+
)
81+
.then((data) => {
82+
if (data.errno) {
83+
throw new Error(data);
84+
}
85+
return data.uploadid;
86+
});
87+
const body = new FormData();
88+
if (content instanceof Blob) {
89+
// 分片上传
90+
body.append("file", content);
91+
} else {
92+
body.append("file", new Blob([content]));
93+
}
94+
95+
await this.fs
96+
.request(
97+
`${
98+
`https://d.pcs.baidu.com/rest/2.0/pcs/superfile2?method=upload&access_token=${this.fs.accessToken}` +
99+
`&type=tmpfile&path=`
100+
}${this.path}&uploadid=${uploadid}&partseq=0`,
101+
{
102+
method: "POST",
103+
body,
104+
}
105+
)
106+
.then((data) => {
107+
if (data.errno) {
108+
throw new Error(data);
109+
}
110+
return data;
111+
});
112+
// 创建文件
113+
urlencoded = new URLSearchParams();
114+
urlencoded.append("path", this.path);
115+
urlencoded.append("size", size);
116+
urlencoded.append("isdir", "0");
117+
urlencoded.append("block_list", JSON.stringify(blockList));
118+
urlencoded.append("uploadid", uploadid);
119+
urlencoded.append("rtype", "3");
120+
return this.fs
121+
.request(
122+
`https://pan.baidu.com/rest/2.0/xpan/file?method=create&access_token=${this.fs.accessToken}`,
123+
{
124+
method: "POST",
125+
headers: myHeaders,
126+
body: urlencoded,
127+
}
128+
)
129+
.then((data) => {
130+
if (data.errno) {
131+
throw new Error(data);
132+
}
133+
return Promise.resolve();
134+
});
135+
}
136+
}

pkg/filesystem/factory.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1+
import BaiduFileSystem from "./baidu/baidu";
12
import FileSystem from "./filesystem";
23
import WebDAVFileSystem from "./webdav/webdav";
34
import ZipFileSystem from "./zip/zip";
45

5-
export type FileSystemType = "zip" | "webdav";
6+
export type FileSystemType = "zip" | "webdav" | "baidu-netdsik";
67

78
export type FileSystemParams = {
89
[key: string]: {
910
title: string;
10-
type?: "select";
11+
type?: "select" | "authorize";
1112
options?: string[];
1213
};
1314
};
@@ -27,6 +28,9 @@ export default class FileSystemFactory {
2728
params.password
2829
);
2930
break;
31+
case "baidu-netdsik":
32+
fs = new BaiduFileSystem();
33+
break;
3034
default:
3135
throw new Error("not found filesystem");
3236
}
@@ -45,6 +49,7 @@ export default class FileSystemFactory {
4549
username: { title: "用户名" },
4650
password: { title: "密码" },
4751
},
52+
"baidu-netdsik": {},
4853
};
4954
}
5055
}

pkg/filesystem/filesystem.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export interface File {
2+
fsid?: number;
23
// 文件名
34
name: string;
45
// 文件路径
@@ -30,7 +31,7 @@ export default interface FileSystem {
3031
// 授权验证
3132
verify(): Promise<void>;
3233
// 打开文件
33-
open(path: string): Promise<FileReader>;
34+
open(file: File): Promise<FileReader>;
3435
// 打开目录
3536
openDir(path: string): Promise<FileSystem>;
3637
// 创建文件

pkg/filesystem/webdav/webdav.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ export default class WebDAVFileSystem implements FileSystem {
3030
return Promise.resolve();
3131
}
3232

33-
open(path: string): Promise<FileReader> {
33+
open(file: File): Promise<FileReader> {
34+
const path = file.name;
3435
return Promise.resolve(
3536
new WebDAVFileReader(this.client, this.getPath(path))
3637
);

pkg/filesystem/zip/zip.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ export default class ZipFileSystem implements FileSystem {
2121
return Promise.resolve();
2222
}
2323

24-
open(path: string): Promise<FileReader> {
24+
open(info: File): Promise<FileReader> {
25+
const path = info.name;
2526
const file = this.zip.file(path);
2627
if (file) {
2728
return Promise.resolve(new ZipFileReader(file));

0 commit comments

Comments
 (0)