|
@@ -0,0 +1,241 @@
|
|
|
+import { existsSync, lstatSync, mkdirSync, readdirSync } from 'fs';
|
|
|
+import { Context } from '@midwayjs/koa';
|
|
|
+import { dirname, extname, join, sep } from 'path';
|
|
|
+import { flattenDeep, get, last, head } from 'lodash';
|
|
|
+import { moveSync, removeSync } from 'fs-extra';
|
|
|
+import { Provide, Inject, Config } from '@midwayjs/core';
|
|
|
+import { mongoose } from '@typegoose/typegoose';
|
|
|
+
|
|
|
+/**
|
|
|
+ * 文件上传相关服务
|
|
|
+ */
|
|
|
+@Provide()
|
|
|
+export class FileService {
|
|
|
+ @Inject()
|
|
|
+ ctx: Context;
|
|
|
+ @Config('busboy')
|
|
|
+ uploadConfig;
|
|
|
+ @Config('koa.globalPrefix')
|
|
|
+ routePrefix: string;
|
|
|
+ @Config('dbName')
|
|
|
+ dbName: string;
|
|
|
+
|
|
|
+ /**临时文件表名 */
|
|
|
+ tmpModelName = 'fileTmp';
|
|
|
+ /**临时文件表model */
|
|
|
+ tmpModel: any;
|
|
|
+
|
|
|
+ // #region 递归清理未被使用的上传文件
|
|
|
+ /**
|
|
|
+ * 删除不在文件使用表登记的文件
|
|
|
+ * TODO:
|
|
|
+ * 1.创建临时表,字段为文件地址;
|
|
|
+ * 2.获取config中配置的${表名}.${字段名}.找到所有文件对象,并把uri拿出来存在临时表中
|
|
|
+ * 3.获取存放根目录地址,递归检索目录下的文件,并查询下文件在临时表中是否存在:
|
|
|
+ * 若存在,则文件保留,跳过处理;
|
|
|
+ * 若不存在,则删除文件.文件可能被替换
|
|
|
+ * 4.并且,在处理文件过程中,禁用上传功能
|
|
|
+ */
|
|
|
+ async deleteNoUseFile() {
|
|
|
+ const realDir = this.uploadConfig.realdir;
|
|
|
+ // 制作临时表及当前使用中的文件地址复制
|
|
|
+ await this.createTmpModel();
|
|
|
+ // 递归处理所有文件
|
|
|
+ this.recursionFindFile(realDir);
|
|
|
+ // 删除临时表
|
|
|
+ await this.deleteTmpModel();
|
|
|
+ }
|
|
|
+ /**创建临时文件表 */
|
|
|
+ async createTmpModel() {
|
|
|
+ const connect = mongoose.connections.find(
|
|
|
+ f => get(f, 'name') === this.dbName
|
|
|
+ );
|
|
|
+ const schema = new mongoose.Schema({
|
|
|
+ uri: String,
|
|
|
+ });
|
|
|
+ this.tmpModel = connect.model(this.tmpModelName, schema, this.tmpModelName);
|
|
|
+ const columns = get(this.uploadConfig, 'columns', []);
|
|
|
+ // 处理图片字段,将uri复制到临时表里
|
|
|
+ for (const c of columns) {
|
|
|
+ const arr = c.split('.');
|
|
|
+ const modelName: string = head(arr);
|
|
|
+ const columnName: string = last(arr);
|
|
|
+ const models = connect.models;
|
|
|
+ // 获取model
|
|
|
+ const model = get(models, modelName);
|
|
|
+ if (!model) continue;
|
|
|
+ // 查询数据
|
|
|
+ const clist = await model
|
|
|
+ .find(
|
|
|
+ {
|
|
|
+ [columnName]: { $exists: true },
|
|
|
+ },
|
|
|
+ `${columnName}`
|
|
|
+ )
|
|
|
+ .lean();
|
|
|
+ // 处理数据
|
|
|
+ let l = clist.map(i => {
|
|
|
+ const icon = get(i, 'icon', []);
|
|
|
+ const l = icon.map(ic => get(ic, 'uri'));
|
|
|
+ return l;
|
|
|
+ });
|
|
|
+ l = flattenDeep(l);
|
|
|
+ l = l.map(i => ({ uri: i }));
|
|
|
+ // 保存到临时表中
|
|
|
+ await this.tmpModel.insertMany(l);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async hasTmpModel() {
|
|
|
+ const connect = mongoose.connections.find(
|
|
|
+ f => get(f, 'name') === this.dbName
|
|
|
+ );
|
|
|
+ const models = connect.models;
|
|
|
+ const model = get(models, this.tmpModelName);
|
|
|
+ return model;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**删除临时文件表 */
|
|
|
+ async deleteTmpModel() {
|
|
|
+ const connect = mongoose.connections.find(
|
|
|
+ f => get(f, 'name') === this.dbName
|
|
|
+ );
|
|
|
+ await connect.dropCollection(this.tmpModelName);
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * 递归找文件
|
|
|
+ * @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.tmpModel.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.getFilesPartsPath());
|
|
|
+ while (shortPath.includes('\\')) {
|
|
|
+ shortPath = shortPath.replace('\\', '/');
|
|
|
+ }
|
|
|
+ return shortPath;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 删除文件
|
|
|
+ * @param path 文件路径
|
|
|
+ */
|
|
|
+ toUnlink(path) {
|
|
|
+ removeSync(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 上传部分
|
|
|
+ getFilesPartsPath() {
|
|
|
+ return `${this.routePrefix}/files`;
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * 获取文件短路径
|
|
|
+ * @param dirs 文件存储路径
|
|
|
+ * @param filename 文件名
|
|
|
+ */
|
|
|
+ getFileShortPath(dirs: Array<string>, filename: string) {
|
|
|
+ return `${this.getFilesPartsPath()}/${dirs.join('/')}/${filename}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**文件真实短地址 */
|
|
|
+ getFileShortRealPath() {
|
|
|
+ let originalUrl = this.ctx.request.originalUrl;
|
|
|
+ originalUrl = decodeURI(originalUrl);
|
|
|
+ //先去掉请求前缀
|
|
|
+ originalUrl = originalUrl.replace(this.getFilesPartsPath(), '');
|
|
|
+ const arr = originalUrl.split('/');
|
|
|
+ // 首行为空去掉
|
|
|
+ arr.splice(0, 1);
|
|
|
+ // 最后一个数据为文件,其余的为路径拼成一起就行
|
|
|
+ return arr.join(sep);
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * 移动文件
|
|
|
+ * @param tempPath 临时上传文件位置
|
|
|
+ * @param path 实际文件应处位置
|
|
|
+ */
|
|
|
+ moveFile(tempPath: string, path: string) {
|
|
|
+ moveSync(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
|
|
|
+}
|