feat: 文件添加哈希计算
|
|
@ -38,6 +38,11 @@
|
|||
- .dockerignore 配置哪些文件应该被忽略掉
|
||||
- .gitea/workflows/depoy.yaml 流水线任务的配置文件,语法上与 Github Actions 一致
|
||||
|
||||
## 计划
|
||||
- 双token无感刷新
|
||||
- session/cookie,jwt,sso单点登陆
|
||||
- 大文件上传,断点续传
|
||||
|
||||
## 笔记
|
||||
|
||||
- createUserDto与User分开
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
This favicon was generated using the following font:
|
||||
|
||||
- Font Title: Leckerli One
|
||||
- Font Author: Copyright (c) 2011 Gesine Todt (www.gesine-todt.de hallo@gesine-todt.de), with Reserved Font Names "Leckerli"
|
||||
- Font Source: http://fonts.gstatic.com/s/leckerlione/v20/V8mCoQH8VCsNttEnxnGQ-1itLZxcBtItFw.ttf
|
||||
- Font License: SIL Open Font License, 1.1 (http://scripts.sil.org/OFL))
|
||||
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 473 B |
|
After Width: | Height: | Size: 915 B |
|
|
@ -0,0 +1 @@
|
|||
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
This favicon was generated using the following font:
|
||||
|
||||
- Font Title: Leckerli One
|
||||
- Font Author: Copyright (c) 2011 Gesine Todt (www.gesine-todt.de hallo@gesine-todt.de), with Reserved Font Names "Leckerli"
|
||||
- Font Source: http://fonts.gstatic.com/s/leckerlione/v20/V8mCoQH8VCsNttEnxnGQ-1itLZxcBtItFw.ttf
|
||||
- Font License: SIL Open Font License, 1.1 (http://scripts.sil.org/OFL))
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
This favicon was generated using the following font:
|
||||
|
||||
- Font Title: Leckerli One
|
||||
- Font Author: Copyright (c) 2011 Gesine Todt (www.gesine-todt.de hallo@gesine-todt.de), with Reserved Font Names "Leckerli"
|
||||
- Font Source: http://fonts.gstatic.com/s/leckerlione/v20/V8mCoQH8VCsNttEnxnGQ-1itLZxcBtItFw.ttf
|
||||
- Font License: SIL Open Font License, 1.1 (http://scripts.sil.org/OFL))
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
This favicon was generated using the following font:
|
||||
|
||||
- Font Title: Leckerli One
|
||||
- Font Author: Copyright (c) 2011 Gesine Todt (www.gesine-todt.de hallo@gesine-todt.de), with Reserved Font Names "Leckerli"
|
||||
- Font Source: http://fonts.gstatic.com/s/leckerlione/v20/V8mCoQH8VCsNttEnxnGQ-1itLZxcBtItFw.ttf
|
||||
- Font License: SIL Open Font License, 1.1 (http://scripts.sil.org/OFL))
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
This favicon was generated using the following font:
|
||||
|
||||
- Font Title: Leckerli One
|
||||
- Font Author: Copyright (c) 2011 Gesine Todt (www.gesine-todt.de hallo@gesine-todt.de), with Reserved Font Names "Leckerli"
|
||||
- Font Source: http://fonts.gstatic.com/s/leckerlione/v20/V8mCoQH8VCsNttEnxnGQ-1itLZxcBtItFw.ttf
|
||||
- Font License: SIL Open Font License, 1.1 (http://scripts.sil.org/OFL))
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
This favicon was generated using the following font:
|
||||
|
||||
- Font Title: Leckerli One
|
||||
- Font Author: Copyright (c) 2011 Gesine Todt (www.gesine-todt.de hallo@gesine-todt.de), with Reserved Font Names "Leckerli"
|
||||
- Font Source: http://fonts.gstatic.com/s/leckerlione/v20/V8mCoQH8VCsNttEnxnGQ-1itLZxcBtItFw.ttf
|
||||
- Font License: SIL Open Font License, 1.1 (http://scripts.sil.org/OFL))
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
This favicon was generated using the following font:
|
||||
|
||||
- Font Title: Leckerli One
|
||||
- Font Author: Copyright (c) 2011 Gesine Todt (www.gesine-todt.de hallo@gesine-todt.de), with Reserved Font Names "Leckerli"
|
||||
- Font Source: http://fonts.gstatic.com/s/leckerlione/v20/V8mCoQH8VCsNttEnxnGQ-1itLZxcBtItFw.ttf
|
||||
- Font License: SIL Open Font License, 1.1 (http://scripts.sil.org/OFL))
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
This favicon was generated using the following font:
|
||||
|
||||
- Font Title: Leckerli One
|
||||
- Font Author: Copyright (c) 2011 Gesine Todt (www.gesine-todt.de hallo@gesine-todt.de), with Reserved Font Names "Leckerli"
|
||||
- Font Source: http://fonts.gstatic.com/s/leckerlione/v20/V8mCoQH8VCsNttEnxnGQ-1itLZxcBtItFw.ttf
|
||||
- Font License: SIL Open Font License, 1.1 (http://scripts.sil.org/OFL))
|
||||
|
After Width: | Height: | Size: 7.1 KiB |
|
|
@ -0,0 +1 @@
|
|||
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
This favicon was generated using the following font:
|
||||
|
||||
- Font Title: Leckerli One
|
||||
- Font Author: Copyright (c) 2011 Gesine Todt (www.gesine-todt.de hallo@gesine-todt.de), with Reserved Font Names "Leckerli"
|
||||
- Font Source: http://fonts.gstatic.com/s/leckerlione/v20/V8mCoQH8VCsNttEnxnGQ-1itLZxcBtItFw.ttf
|
||||
- Font License: SIL Open Font License, 1.1 (http://scripts.sil.org/OFL))
|
||||
|
After Width: | Height: | Size: 21 KiB |
|
|
@ -0,0 +1 @@
|
|||
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
||||
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
|
@ -0,0 +1,6 @@
|
|||
This favicon was generated using the following font:
|
||||
|
||||
- Font Title: Leckerli One
|
||||
- Font Author: Copyright (c) 2011 Gesine Todt (www.gesine-todt.de hallo@gesine-todt.de), with Reserved Font Names "Leckerli"
|
||||
- Font Source: http://fonts.gstatic.com/s/leckerlione/v20/V8mCoQH8VCsNttEnxnGQ-1itLZxcBtItFw.ttf
|
||||
- Font License: SIL Open Font License, 1.1 (http://scripts.sil.org/OFL))
|
||||
|
After Width: | Height: | Size: 7.1 KiB |
|
|
@ -0,0 +1 @@
|
|||
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
This favicon was generated using the following font:
|
||||
|
||||
- Font Title: Leckerli One
|
||||
- Font Author: Copyright (c) 2011 Gesine Todt (www.gesine-todt.de hallo@gesine-todt.de), with Reserved Font Names "Leckerli"
|
||||
- Font Source: http://fonts.gstatic.com/s/leckerlione/v20/V8mCoQH8VCsNttEnxnGQ-1itLZxcBtItFw.ttf
|
||||
- Font License: SIL Open Font License, 1.1 (http://scripts.sil.org/OFL))
|
||||
|
After Width: | Height: | Size: 7.1 KiB |
|
|
@ -0,0 +1 @@
|
|||
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
||||
|
After Width: | Height: | Size: 7.1 KiB |
|
|
@ -1,6 +1,7 @@
|
|||
import { BaseEntity } from '@/database';
|
||||
import { FileCategory } from '@/storage/fileCategory';
|
||||
import { ApiHideProperty } from '@nestjs/swagger';
|
||||
import { Exclude } from 'class-transformer';
|
||||
import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm';
|
||||
|
||||
@Entity({ orderBy: { id: 'DESC' } })
|
||||
|
|
@ -54,6 +55,13 @@ export class File extends BaseEntity {
|
|||
@Column({ comment: '文件后缀' })
|
||||
extension: string;
|
||||
|
||||
/**
|
||||
* 文件类型
|
||||
* @example 1
|
||||
*/
|
||||
@Column({ comment: '类型(1: 文本,2: 图片,3: 音频,4: 视频,5: 其他)', nullable: true })
|
||||
type: number;
|
||||
|
||||
/**
|
||||
* 分类
|
||||
*/
|
||||
|
|
@ -66,6 +74,8 @@ export class File extends BaseEntity {
|
|||
* 分类ID
|
||||
* @example 0
|
||||
*/
|
||||
@Exclude()
|
||||
@ApiHideProperty()
|
||||
@Column({ comment: '分类ID', nullable: true })
|
||||
categoryId: number;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@ import { UpdateFileDto } from './dto/update-file.dto';
|
|||
import { File } from './entities/file.entity';
|
||||
import { FindFileDto } from './dto/find-file.dto';
|
||||
import { FileCategoryService } from '../fileCategory';
|
||||
import { createReadStream } from 'fs';
|
||||
import { createHash } from 'crypto';
|
||||
import { getTypeByMimetype } from './util';
|
||||
|
||||
@Injectable()
|
||||
export class FileService extends BaseService {
|
||||
|
|
@ -23,13 +26,17 @@ export class FileService extends BaseService {
|
|||
* @returns
|
||||
*/
|
||||
async create(uploadFile: Express.Multer.File) {
|
||||
const { originalname: name, mimetype, size, path: hash } = uploadFile;
|
||||
const relativePath = relative(this.config.uploadDir, uploadFile.path).split(sep).join('/');
|
||||
const path = `${this.config.uploadPrefix}/${relativePath}`;
|
||||
const { originalname: name, mimetype, size } = uploadFile;
|
||||
const { uploadDir, uploadPrefix } = this.config;
|
||||
const relativePath = relative(uploadDir, uploadFile.path).split(sep).join('/');
|
||||
const path = `${uploadPrefix}/${relativePath}`;
|
||||
const extension = extname(uploadFile.originalname);
|
||||
const description = '';
|
||||
const hash = await this.hashByFilePath(uploadFile.path);
|
||||
const type = getTypeByMimetype(mimetype)
|
||||
const file = this.repository.create({
|
||||
name,
|
||||
type,
|
||||
mimetype,
|
||||
size,
|
||||
hash,
|
||||
|
|
@ -41,7 +48,28 @@ export class FileService extends BaseService {
|
|||
return file.id;
|
||||
}
|
||||
|
||||
findMany(findFileDto: FindFileDto) {
|
||||
/**
|
||||
* 从文件路径读取流,进行MD5哈希
|
||||
* @param path 文件路径
|
||||
* @returns
|
||||
*/
|
||||
hashByFilePath(path: string): Promise<string> {
|
||||
const hash = createHash('md5');
|
||||
const stream = createReadStream(path);
|
||||
return new Promise((res, rej) => {
|
||||
stream.on('data', (chunk) => {
|
||||
hash.update(chunk);
|
||||
});
|
||||
stream.on('end', () => {
|
||||
res(hash.digest('hex'));
|
||||
});
|
||||
stream.on('error', () => {
|
||||
rej('获取文件哈希值失败');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async findMany(findFileDto: FindFileDto) {
|
||||
const { page, size, name, categoryId } = findFileDto;
|
||||
const { skip, take } = this.formatPagination(page, size, true);
|
||||
const where: FindOptionsWhere<File> = {};
|
||||
|
|
@ -51,7 +79,20 @@ export class FileService extends BaseService {
|
|||
if (categoryId) {
|
||||
where.categoryId = categoryId;
|
||||
}
|
||||
return this.repository.findAndCount({ skip, take, where });
|
||||
return this.repository.findAndCount({
|
||||
skip,
|
||||
take,
|
||||
where,
|
||||
relations: {
|
||||
category: true,
|
||||
},
|
||||
select: {
|
||||
category: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
findOne(id: number) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* 文件类型
|
||||
*/
|
||||
enum FileType {
|
||||
/**
|
||||
* 文本
|
||||
*/
|
||||
TEXT = 1,
|
||||
/**
|
||||
* 图片
|
||||
*/
|
||||
IMAGE = 2,
|
||||
/**
|
||||
* 音频
|
||||
*/
|
||||
AUDIO = 3,
|
||||
/**
|
||||
* 视频
|
||||
*/
|
||||
VIDEO = 4,
|
||||
/**
|
||||
* 其他
|
||||
*/
|
||||
OTHER = 5,
|
||||
}
|
||||
|
||||
export function getTypeByMimetype(mimetype: string) {
|
||||
const [type] = mimetype.split('/');
|
||||
return FileType[type.toLowerCase()] ?? FileType.OTHER;
|
||||
}
|
||||
|
|
@ -29,5 +29,5 @@ export class CreateRoleDto {
|
|||
*/
|
||||
@IsOptional()
|
||||
@IsInt({ each: true })
|
||||
menuIds: number[];
|
||||
menuIds?: number[];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ export class Role extends BaseEntity {
|
|||
|
||||
/**
|
||||
* 菜单ID数组
|
||||
* @example [1]
|
||||
*/
|
||||
@RelationId('menus')
|
||||
menuIds: number[];
|
||||
|
|
|
|||