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; // #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 }