123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225 |
- import { Config, Inject, Provide } from '@midwayjs/decorator';
- import { existsSync, lstatSync, mkdirSync, readdirSync, renameSync, unlinkSync } from 'fs';
- import { Context } from '@midwayjs/koa';
- import { dirname, extname, join, sep } from 'path';
- import { ReturnModelType, mongoose } from '@typegoose/typegoose';
- import { isObject, get, pick, flattenDeep } from 'lodash';
- import { GetModel } from '../util/getModel';
- import { InjectEntityModel } from '@midwayjs/typegoose';
- import { UseFile } from '../entity/useFile.entity';
- interface ScanModelFileValue {
- model: any;
- projection: object;
- }
- interface ScanModelFileResult {
- [key: string]: ScanModelFileValue;
- }
- const limit = 50;
- /**
- * 文件上传相关服务
- */
- @Provide()
- export class FileService {
- @Inject()
- ctx: Context;
- @Config('koa.globalPrefix')
- globalPrefix: string;
- @Config('upload')
- uploadConfig;
- @InjectEntityModel(UseFile)
- UseFileModel: ReturnModelType<typeof UseFile>;
- // #region 递归清理未被使用的上传文件
- /**
- * 删除不在文件使用表登记的文件
- */
- async deleteNoUseFile() {
- const realDir = this.uploadConfig.realdir;
- this.recursionFindFile(realDir);
- }
- /**
- *
- * @param basePath 基础路径
- * @param list 基础路径下的所有内容
- */
- async recursionFindFile(basePath) {
- const dirExists = existsSync(basePath);
- if (!dirExists) return;
- const list = readdirSync(basePath);
- for (const f of list) {
- const thisPath = join(basePath, f);
- // 文件夹就继续递归找
- if (this.isDir(thisPath)) this.recursionFindFile(thisPath);
- else if (this.isFile(thisPath)) {
- // 文件,需要到表里找是否存在,存在保留,不存在就删除
- const shortPath = this.realPathTurnToShortPath(thisPath);
- const count = await this.UseFileModel.count({ uri: shortPath });
- if (count <= 0) this.toUnlink(thisPath);
- }
- }
- }
- /**
- * 真实路径=>短地址
- * @param realPath 文件真实路径
- * @returns string 短地址
- */
- realPathTurnToShortPath(realPath: string) {
- const realDir = this.uploadConfig.realdir;
- let shortPath = realPath.replace(realDir, `${this.globalPrefix}/files`);
- while (shortPath.includes('\\')) {
- shortPath = shortPath.replace('\\', '/');
- }
- return shortPath;
- }
- /**
- * 删除文件
- * @param path 文件路径
- */
- toUnlink(path) {
- unlinkSync(path);
- }
- /**
- * 判断路径是否存在
- * @param path 路径
- * @returns boolean: true-存在/false-不存在
- */
- isExists(path) {
- return existsSync(path);
- }
- /**
- * 判断是否是文件夹
- * @param path 路径
- * @returns boolean: true-是文件夹
- */
- isDir(path) {
- const f = lstatSync(path);
- return f.isDirectory();
- }
- /**
- * 判断是否是文件
- * @param path 路径
- * @returns boolean: true-是文件
- */
- isFile(path) {
- const f = lstatSync(path);
- return f.isFile();
- }
- // #endregion
- // #region 写入文件统计地址表
- /**文件统计地址表重写, 由 scanModelHaveFile()提供参数*/
- async setFileUriFromDefaultDataBase(scanResult: ScanModelFileResult) {
- // 清空表内容,重写
- await this.UseFileModel.deleteMany({});
- for (const mn in scanResult) {
- const obj = scanResult[mn];
- const { model, projection } = obj;
- // 获取表总数量
- const total = await model.count();
- // 计算循环次数
- const skip = 0;
- const times = Math.ceil(total / limit);
- for (let i = 0; i < times; i++) {
- // 查数据
- const r = await model.find({}, projection).skip(skip).limit(limit).lean();
- // 整理批量添加数据格式
- let filesUri = r.map(i => {
- const arr = [];
- for (const prop in projection) {
- const targetVal = i[prop];
- const uris = targetVal.map(ti => pick(ti, 'uri'));
- arr.push(...uris);
- }
- return arr;
- });
- filesUri = flattenDeep(filesUri);
- //批量添加
- await this.UseFileModel.insertMany(filesUri);
- }
- }
- }
- /**扫描注册的model中是否有File类型字段,以Object的形式提取出来*/
- scanModelHaveFile(): ScanModelFileResult {
- const modelNames = this.uploadConfig.modelNames || [];
- const models = modelNames.map(i => GetModel(i));
- const result: ScanModelFileResult = {};
- for (const model of models) {
- const s = model['schema'];
- const fields: object = s['tree'];
- const projection = {};
- for (const f in fields) {
- const field = fields[f];
- if (isObject(field)) {
- const type = get(field, 'type');
- if (type && type === mongoose.Schema.Types['File']) {
- projection[f] = 1;
- }
- }
- }
- if (Object.keys(projection).length > 0) {
- result[model.modelName] = { model, projection };
- }
- }
- return result;
- }
- // #endregion
- // #region 上传部分
- /**文件真实短地址 */
- getFileShortRealPath() {
- let originalUrl = this.ctx.request.originalUrl;
- //先去掉请求前缀
- originalUrl = originalUrl.replace(this.globalPrefix, '');
- // 再去掉files
- originalUrl = originalUrl.replace('/files', '');
- const arr = originalUrl.split('/');
- // 首行为空去掉
- arr.splice(0, 1);
- // 最后一个数据为文件,其余的为路径拼成一起就行
- return arr.join(sep);
- }
- /**
- * 移动文件
- * @param tempPath 临时上传文件位置
- * @param path 实际文件应处位置
- */
- moveFile(tempPath: string, path: string) {
- renameSync(tempPath, path);
- }
- // 创建文件夹
- mkdir(dn: string) {
- if (existsSync(dn)) {
- return true;
- }
- if (this.mkdir(dirname(dn))) {
- mkdirSync(dn);
- return true;
- }
- }
- /**获取文件名后缀 */
- getExt(name: string) {
- return extname(name);
- }
- /**获取年月日时分秒, 格式: 年月日时分秒 */
- getNowDateTime() {
- const date = new Date();
- const y = date.getFullYear();
- const m = date.getMonth() + 1;
- const mstr = m < 10 ? '0' + m : m;
- const d = date.getDate();
- const dstr = d < 10 ? '0' + d : d;
- const h = date.getHours();
- const hstr = h < 10 ? '0' + h : h;
- const minute = date.getMinutes();
- const minutestr = minute < 10 ? '0' + minute : minute;
- const second = date.getSeconds();
- const secondstr = second < 10 ? '0' + second : second;
- return `${y}${mstr}${dstr}${hstr}${minutestr}${secondstr}`;
- }
- // #endregion
- }
|