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({
|
export const HttpClient = axios.create({
|
||||||
baseURL: process.env.SERVER_API_URL,
|
baseURL: process.env.SERVER_API_URL,
|
||||||
timeout: 10 * 1000,
|
timeout: 10 * 60 * 1000,
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
}) as AxiosInstance;
|
}) 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 { ConfigService } from '@nestjs/config';
|
||||||
|
|
||||||
|
import { AliyunOssClient } from './aliyun.client';
|
||||||
import { LocalOssClient } from './local.client';
|
import { LocalOssClient } from './local.client';
|
||||||
import { OssClient } from './oss.client';
|
import { OssClient } from './oss.client';
|
||||||
|
|
||||||
export { OssClient };
|
export { OssClient };
|
||||||
|
|
||||||
export const getOssClient = (configService: ConfigService): OssClient => {
|
export const getOssClient = (configService: ConfigService): OssClient => {
|
||||||
|
if (configService.get('oss.aliyun.enable')) {
|
||||||
|
return new AliyunOssClient(configService);
|
||||||
|
}
|
||||||
|
|
||||||
if (configService.get('oss.local.enable')) {
|
if (configService.get('oss.local.enable')) {
|
||||||
return new LocalOssClient(configService);
|
return new LocalOssClient(configService);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { BaseOssClient, FileQuery } from './oss.client';
|
||||||
export const FILE_DEST = '/' + 'static';
|
export const FILE_DEST = '/' + 'static';
|
||||||
export const FILE_ROOT_PATH = path.join(__dirname, '../../../', FILE_DEST);
|
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) => {
|
return new Promise((resolve) => {
|
||||||
const readStream = fs.createReadStream(filepath);
|
const readStream = fs.createReadStream(filepath);
|
||||||
readStream.on('end', () => {
|
readStream.on('end', () => {
|
||||||
|
|
Loading…
Reference in New Issue