mirror of https://github.com/fantasticit/think.git
server: add ali-oss
parent
757eac171a
commit
ed03f1e90f
|
@ -10,7 +10,7 @@ interface AxiosInstance extends Axios {
|
|||
|
||||
export const HttpClient = axios.create({
|
||||
baseURL: process.env.SERVER_API_URL,
|
||||
timeout: 10 * 1000,
|
||||
timeout: 10 * 60 * 1000,
|
||||
withCredentials: true,
|
||||
}) as AxiosInstance;
|
||||
|
||||
|
|
|
@ -0,0 +1,152 @@
|
|||
import { FILE_CHUNK_SIZE } from '@think/domains';
|
||||
import * as AliyunOSS from 'ali-oss';
|
||||
import * as fs from 'fs-extra';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
|
||||
import { pipeWriteStream } from './local.client';
|
||||
import { BaseOssClient, FileQuery } from './oss.client';
|
||||
|
||||
export class AliyunOssClient extends BaseOssClient {
|
||||
private client: AliyunOSS | null;
|
||||
|
||||
/**
|
||||
* 构建 ali-oss 客户端
|
||||
* @returns
|
||||
*/
|
||||
private ensureAliyunOssClient(): AliyunOSS {
|
||||
if (this.client) {
|
||||
return this.client;
|
||||
}
|
||||
|
||||
const config = this.configService.get('oss.aliyun.config');
|
||||
|
||||
try {
|
||||
this.client = new AliyunOSS(config);
|
||||
return this.client;
|
||||
} catch (err) {
|
||||
console.log('无法启动阿里云存储服务,请检查阿里云 OSS 配置是否正确');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上传文件名
|
||||
* @param md5
|
||||
* @param filename
|
||||
* @returns
|
||||
*/
|
||||
private getInOssFileName(md5, filename) {
|
||||
return `/think/${md5}/${filename}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查文件是否已存储到 oss
|
||||
* @param md5
|
||||
* @param filename
|
||||
* @returns
|
||||
*/
|
||||
private async checkIfAlreadyInOss(md5, filename) {
|
||||
this.ensureAliyunOssClient();
|
||||
const inOssFileName = this.getInOssFileName(md5, filename);
|
||||
const ifExist = await this.client.head(inOssFileName).catch(() => false);
|
||||
|
||||
if (ifExist) {
|
||||
return ifExist.res.requestUrls[0];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件临时存储路径
|
||||
* @param md5
|
||||
* @returns
|
||||
*/
|
||||
private getStoreDir(md5: string) {
|
||||
const tmpdir = os.tmpdir();
|
||||
const dir = path.join(tmpdir, md5);
|
||||
fs.ensureDirSync(dir);
|
||||
return dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传小文件
|
||||
* @param file
|
||||
* @param query
|
||||
* @returns
|
||||
*/
|
||||
async uploadFile(file: Express.Multer.File, query: FileQuery): Promise<string> {
|
||||
const client = this.ensureAliyunOssClient();
|
||||
const { filename, md5 } = query;
|
||||
|
||||
const maybeOssURL = await this.checkIfAlreadyInOss(md5, filename);
|
||||
if (maybeOssURL) {
|
||||
return maybeOssURL;
|
||||
}
|
||||
|
||||
const inOssFileName = this.getInOssFileName(md5, filename);
|
||||
const res = await client.put(inOssFileName, file.buffer);
|
||||
return res.url;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将切片临时存储到服务器
|
||||
* FIXME: 阿里云的文档没看懂,故做成这种服务器中转的蠢模式
|
||||
* @param file
|
||||
* @param query
|
||||
* @returns
|
||||
*/
|
||||
async uploadChunk(file: Express.Multer.File, query: FileQuery): Promise<string | void> {
|
||||
const { md5, filename, chunkIndex } = query;
|
||||
|
||||
if (!('chunkIndex' in query)) {
|
||||
throw new Error('请指定 chunkIndex');
|
||||
}
|
||||
|
||||
const maybeOssURL = await this.checkIfAlreadyInOss(md5, filename);
|
||||
if (maybeOssURL) {
|
||||
return maybeOssURL;
|
||||
}
|
||||
|
||||
const dir = this.getStoreDir(md5);
|
||||
const chunksDir = path.join(dir, 'chunks');
|
||||
fs.ensureDirSync(chunksDir);
|
||||
fs.writeFileSync(path.join(chunksDir, '' + chunkIndex), file.buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并切片后上传到阿里云
|
||||
* FIXME: 阿里云的文档没看懂,故做成这种服务器中转的蠢模式
|
||||
* @param query
|
||||
* @returns
|
||||
*/
|
||||
async mergeChunk(query: FileQuery): Promise<string> {
|
||||
const { filename, md5 } = query;
|
||||
|
||||
this.ensureAliyunOssClient();
|
||||
const inOssFileName = this.getInOssFileName(md5, filename);
|
||||
|
||||
const dir = this.getStoreDir(md5);
|
||||
const absoluteFilepath = path.join(dir, filename);
|
||||
const chunksDir = path.join(dir, 'chunks');
|
||||
const chunks = fs.readdirSync(chunksDir);
|
||||
chunks.sort((a, b) => Number(a) - Number(b));
|
||||
|
||||
await Promise.all(
|
||||
chunks.map((chunk, index) => {
|
||||
return pipeWriteStream(
|
||||
path.join(chunksDir, chunk),
|
||||
fs.createWriteStream(absoluteFilepath, {
|
||||
start: index * FILE_CHUNK_SIZE,
|
||||
end: (index + 1) * FILE_CHUNK_SIZE,
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
fs.removeSync(chunksDir);
|
||||
const ret = await this.client.multipartUpload(inOssFileName, absoluteFilepath);
|
||||
fs.removeSync(absoluteFilepath);
|
||||
return ret.res.requestUrls[0];
|
||||
}
|
||||
}
|
|
@ -1,11 +1,16 @@
|
|||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
import { AliyunOssClient } from './aliyun.client';
|
||||
import { LocalOssClient } from './local.client';
|
||||
import { OssClient } from './oss.client';
|
||||
|
||||
export { OssClient };
|
||||
|
||||
export const getOssClient = (configService: ConfigService): OssClient => {
|
||||
if (configService.get('oss.aliyun.enable')) {
|
||||
return new AliyunOssClient(configService);
|
||||
}
|
||||
|
||||
if (configService.get('oss.local.enable')) {
|
||||
return new LocalOssClient(configService);
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import { BaseOssClient, FileQuery } from './oss.client';
|
|||
export const FILE_DEST = '/' + 'static';
|
||||
export const FILE_ROOT_PATH = path.join(__dirname, '../../../', FILE_DEST);
|
||||
|
||||
const pipeWriteStream = (filepath, writeStream): Promise<void> => {
|
||||
export const pipeWriteStream = (filepath, writeStream): Promise<void> => {
|
||||
return new Promise((resolve) => {
|
||||
const readStream = fs.createReadStream(filepath);
|
||||
readStream.on('end', () => {
|
||||
|
|
Loading…
Reference in New Issue