diff --git a/.gitignore b/.gitignore index 6704566..37f0608 100644 --- a/.gitignore +++ b/.gitignore @@ -102,3 +102,8 @@ dist # TernJS port file .tern-port + + +sqliteHistory.json +mysqlHistory.json +database.db \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..1d37f64 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,20 @@ +{ + // 使用 IntelliSense 了解相关属性。 + // 悬停以查看现有属性的描述。 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "pwa-node", + "request": "launch", + "name": "Launch Program", + "skipFiles": [ + "/**" + ], + "program": "${workspaceFolder}/dist/index.js", + "outFiles": [ + "${workspaceFolder}/dist/**/*.js" + ] + } + ] +} \ No newline at end of file diff --git a/api/ApplicationRuleApi.ts b/api/ApplicationRuleApi.ts new file mode 100644 index 0000000..deabd75 --- /dev/null +++ b/api/ApplicationRuleApi.ts @@ -0,0 +1,29 @@ +import { Context } from "koa"; +import ApplicationRuleService from "../service/ApplicationRuleService"; +import config from "../config"; + +const router = {}; + +/** + * 获取目录下的文件列表 + */ +router["GET /applicationRule"] = async function (ctx: Context) { + ctx.body = await ApplicationRuleService.getAll(); +}; + +/** + * 更新或者插入 + */ +router['POST /applicationRule'] = async function (ctx: Context) { + ctx.body = await ApplicationRuleService.saveOrAdd(ctx.request.body); +} + +/** + * 删除 + */ +router["DELETE /applicationRule/:id"] = async function (ctx: Context) { + await ApplicationRuleService.deleteById(ctx.params.id); + ctx.body = ""; +}; + +export default router; diff --git a/api/FileApi.ts b/api/FileApi.ts new file mode 100644 index 0000000..b4e0e9d --- /dev/null +++ b/api/FileApi.ts @@ -0,0 +1,28 @@ +import { Context } from "koa"; +import FileService from "../service/FileService"; +import config from "../config"; + +const router = {}; + +/** + * 获取目录下的文件列表 + */ +router["GET /file/query"] = async function (ctx: Context) { + ctx.body = await FileService.readPath(ctx.query.path as string, ctx.query.showHidden === '1'); +}; + +/** + *是否windows + */ +router['GET /file/isWindows'] = async function (ctx:Context) { + ctx.body=config.isWindows; +} + +/** + * 检查路径是否存在 + */ +router["GET /file/path/exist"] = async function (ctx: Context) { + ctx.body = await FileService.checkExist(ctx.query.path as string); +}; + +export default router; diff --git a/api/RenamerApi.ts b/api/RenamerApi.ts new file mode 100644 index 0000000..e22e861 --- /dev/null +++ b/api/RenamerApi.ts @@ -0,0 +1,22 @@ +import { Context } from "koa"; +import RenamerService from "../service/RenamerService"; + +const router = {}; + +/** + * 预览文件修改后的状态 + */ +router["POST /renamer/preview"] = async function (ctx: Context) { + ctx.body = await RenamerService.preview(ctx.request.body.fileList, ctx.request.body.ruleList); +}; + +/** + * 提交修改 + */ +router["POST /renamer/submit"] = async function (ctx: Context) { + ctx.body = await RenamerService.rename(ctx.request.body.fileList, ctx.request.body.changedFileList); +}; + + + +export default router; diff --git a/config.ts b/config.ts new file mode 100644 index 0000000..6ed2cdf --- /dev/null +++ b/config.ts @@ -0,0 +1,45 @@ +import * as path from 'path'; + +//后台所在绝对路径 +const rootPath = path.resolve(__dirname, '..'); + +let config = { + rootPath, + port: process.env.PORT ? parseInt(process.env.PORT) : 8089, + urlPrefix: '/openRenamer/api', + //是否为windows平台 + isWindows: process.platform.toLocaleLowerCase().includes("win"), + //sqlite相关配置 + sqlite: { + enable: false, //是否启用sqlite + //相对于项目根目录 + filePath: "database.db", + //sql存放地址,用于执行sql + sqlFolder: "sqliteSqls" + }, + //mysql相关配置 + mysql: { + enable: true, //是否启用mysql + sqlFolder: "mysqlSqls", + connection: { + host: "localhost", + port: 3306, + user: "root", + password: "123456", + database: "qiezi", + } + + }, + bodyLimit: { + formLimit: '2mb', + urlencoded: true, + multipart: true, + formidable: { + uploadDir: path.join(rootPath, 'files', 'temp', 'uploads'), + keepExtenstions: true, + maxFieldsSize: 1024 * 1024 + } + } +}; + +export default config; diff --git a/dao/ApplicationRuleDao.ts b/dao/ApplicationRuleDao.ts new file mode 100644 index 0000000..41383b6 --- /dev/null +++ b/dao/ApplicationRuleDao.ts @@ -0,0 +1,52 @@ +import ErrorHelper from "../util/ErrorHelper"; +import ApplicationRule from "../entity/dto/ApplicationRule"; +import SqliteHelper from "../util/SqliteHelper"; + +export default class ApplicationRuleDao { + /** + * 查询所有 + * @param obj + * @returns + */ + static async getAll(): Promise> { + let res = await SqliteHelper.pool.all('select id,createdDate,updatedDate,name,comment,content from application_rule'); + return res; + } + + + /** + * 新增 + * @param obj + * @returns + */ + static async addOne(obj: ApplicationRule): Promise { + let res = await SqliteHelper.pool.run('insert into application_rule(createdDate,updatedDate,name,comment,content) values(?,?,?,?,?)' + , obj.createdDate, obj.updatedDate, obj.name, obj.comment, obj.content); + return res.lastID; + } + + /** + * 更新 + * @param obj + */ + static async updateOne(obj: ApplicationRule): Promise { + let res = await SqliteHelper.pool.run('update application_rule set updatedDate=?,name=?,comment=?,content=? where id=?' + , obj.updatedDate, obj.name, obj.comment, obj.content, obj.id); + if (res.changes == 0) { + throw ErrorHelper.Error404("数据不存在"); + } + } + + /** + * 删除 + * @param id + */ + static async delete(id: number): Promise { + let res = await SqliteHelper.pool.run('delete from application_rule where id=?', id); + if (res.changes == 0) { + throw ErrorHelper.Error404("数据不存在"); + } + } + + +} \ No newline at end of file diff --git a/entity/dto/ApplicationRule.ts b/entity/dto/ApplicationRule.ts new file mode 100644 index 0000000..8692c0b --- /dev/null +++ b/entity/dto/ApplicationRule.ts @@ -0,0 +1,23 @@ +export default class ApplicationRule { + /** + 创建时间 + */ + createdDate: number; + /** + 更新时间 + */ + updatedDate: number; + id: number; + /** + 名称 + */ + name: string; + /** + 说明 + */ + comment: string; + /** + 规则内容,json序列化后 + */ + content: string; +} \ No newline at end of file diff --git a/entity/po/HostPo.ts b/entity/po/HostPo.ts new file mode 100644 index 0000000..5f146cd --- /dev/null +++ b/entity/po/HostPo.ts @@ -0,0 +1,30 @@ +export default class HostPo { + /** + * id + */ + id: number; + /** + * 标识key,用于表明身份 + */ + key: string; + /** + * 密钥 + */ + secret: string; + /** + * 网站名称 + */ + name: string; + /** + * 网站域名 + */ + host: string; + /** + * pv + */ + pv: number; + /** + * uv + */ + uv: number; +} \ No newline at end of file diff --git a/entity/vo/FileObj.ts b/entity/vo/FileObj.ts new file mode 100644 index 0000000..aa0703b --- /dev/null +++ b/entity/vo/FileObj.ts @@ -0,0 +1,50 @@ +import * as pathUtil from "path"; +export default class FileObj { + /** + * 文件名 + */ + name: string; + /** + * 拓展名 + */ + expandName: string; + /** + * 去掉拓展名后的名字 + */ + realName: string; + /** + * 所属路径 + */ + path: string; + /** + * 是否文件夹 + */ + isFolder: boolean; + /** + * 重命名错误原因 + */ + errorMessage: string; + /** + * 创建时间ms + */ + createdTime: number; + /** + * 更新时间ms + */ + updatedTime: number; + + + constructor(name: string, path, isFolder, createdTime, updatedTime) { + this.name = name; + this.expandName = pathUtil.extname(name); + if (this.expandName.length > 0) { + this.realName = name.substring(0, name.lastIndexOf(".")); + } else { + this.realName = name; + } + this.path = path; + this.isFolder = isFolder; + this.createdTime = createdTime; + this.updatedTime = updatedTime; + } +} \ No newline at end of file diff --git a/entity/vo/RuleObj.ts b/entity/vo/RuleObj.ts new file mode 100644 index 0000000..55122a3 --- /dev/null +++ b/entity/vo/RuleObj.ts @@ -0,0 +1,27 @@ +import DeleteRule from "./rules/DeleteRule"; +import InsertRule from "./rules/InsertRule"; +import SerializationRule from "./rules/SerializationRule"; + +export default class RuleObj { + type: string; + message: string; + /** + * 具体参数 + */ + data: any; + + constructor(data: any) { + this.type = data.type; + this.message = data.message; + switch (this.type) { + case "delete": + this.data = new DeleteRule(data.data); + break; + case "insert": + this.data = new InsertRule(data.data); + break; + default: + this.data = new SerializationRule(data.data); + } + } +} \ No newline at end of file diff --git a/entity/vo/rules/DeleteRule.ts b/entity/vo/rules/DeleteRule.ts new file mode 100644 index 0000000..aacd042 --- /dev/null +++ b/entity/vo/rules/DeleteRule.ts @@ -0,0 +1,92 @@ +import RuleInterface from "./RuleInterface"; +import FileObj from "../FileObj"; +import path from 'path'; + +export default class DeleteRule implements RuleInterface { + /** + * 类别:deletePart:部分删除,deleteAll:全部删除 + */ + type: string; + /** + * 部分删除时的开始信息 + */ + start: DeleteRuleItem; + /** + * 部分删除时的结束信息 + + */ + end: DeleteRuleItem; + /** + * 忽略拓展名,true:忽略,false:不忽略 + */ + ignorePostfix: boolean; + + constructor(data: any) { + this.type = data.type; + this.start = new DeleteRuleItem(data.start); + this.end = new DeleteRuleItem(data.end); + this.ignorePostfix = data.ignorePostfix; + } + + + + deal(file: FileObj): void { + if (this.type === 'deleteAll') { + file.realName = ""; + if (!this.ignorePostfix) { + file.expandName = ""; + } + } else { + let str = file.realName + (this.ignorePostfix ? "" : file.expandName); + let startIndex = this.start.calIndex(str); + let endIndex = this.end.calIndex(str); + if (startIndex < 0 || endIndex < 0) { + return; + } + str = str.substring(0, startIndex) + str.substring(endIndex + 1); + if (this.ignorePostfix) { + file.realName = str; + } else { + file.expandName = path.extname(str); + if (file.expandName.length > 0) { + file.realName = str.substring(0, str.lastIndexOf(".")); + } else { + file.realName = str; + } + } + } + + file.name = file.realName + file.expandName; + } + +} + +class DeleteRuleItem { + /** + * location:位置,text:文本,end:直到末尾 + */ + type: string; + /** + * 对应的值 + */ + value: string; + + constructor(data: any) { + this.type = data.type; + this.value = data.value; + } + + /** + * 计算位置 + */ + calIndex(str: string): number { + if (this.type === 'location') { + return parseInt(this.value) - 1; + } else if (this.type === 'text') { + return str.indexOf(this.value); + } else if (this.type === 'end') { + return str.length - 1; + } + return -1; + } +} diff --git a/entity/vo/rules/InsertRule.ts b/entity/vo/rules/InsertRule.ts new file mode 100644 index 0000000..9f04d58 --- /dev/null +++ b/entity/vo/rules/InsertRule.ts @@ -0,0 +1,66 @@ +import RuleInterface from "./RuleInterface"; +import FileObj from "../FileObj"; +import path from 'path'; + +export default class InsertRule implements RuleInterface { + + /** + * 插入内容 + */ + insertContent: string; + /** + * 操作类别,front:前缀,backend:后缀,at:位置,replace:替换当前文件名 + */ + type: string; + /** + * 当type为at,时的位置,从1开始 + */ + atInput: number; + /** + * 当type为at,时的方向,true:从右到左,false:从左到右 + */ + atIsRightToleft: boolean; + /** + * 忽略拓展名,true:忽略,false:不忽略 + */ + ignorePostfix: boolean; + + constructor(data: any) { + this.insertContent = data.insertContent; + this.type = data.type; + this.atInput = data.atInput; + this.atIsRightToleft = data.atIsRightToleft; + this.ignorePostfix = data.ignorePostfix; + } + + + deal(file: FileObj): void { + let str = this.ignorePostfix ? file.realName : file.name; + switch (this.type) { + case "front": + str = this.insertContent + str; + break; + case "backend": + str = str + this.insertContent; + break; + case "at": + let index = this.atIsRightToleft ? str.length - this.atInput + 1 : this.atInput - 1; + str = str.substring(0, index) + this.insertContent + str.substring(index); + break; + case "replace": + str = this.insertContent; + break; + } + if (this.ignorePostfix) { + file.realName = str; + } else { + file.expandName = path.extname(str); + if (file.expandName.length > 0) { + file.realName = str.substring(0, str.lastIndexOf(".")); + } else { + file.realName = str; + } + } + file.name = file.realName + file.expandName; + } +} \ No newline at end of file diff --git a/entity/vo/rules/RuleInterface.ts b/entity/vo/rules/RuleInterface.ts new file mode 100644 index 0000000..84a6b0f --- /dev/null +++ b/entity/vo/rules/RuleInterface.ts @@ -0,0 +1,6 @@ +import FileObj from "../FileObj"; + +export default interface RuleInterface { + + deal(file: FileObj): void; +} \ No newline at end of file diff --git a/entity/vo/rules/SerializationRule.ts b/entity/vo/rules/SerializationRule.ts new file mode 100644 index 0000000..1df7114 --- /dev/null +++ b/entity/vo/rules/SerializationRule.ts @@ -0,0 +1,80 @@ +import RuleInterface from "./RuleInterface"; +import FileObj from "../FileObj"; +import path from 'path'; + +export default class InsertRule implements RuleInterface { + /** + * 开始位置 + */ + start: number; + /** + * 记录当前的值是多少 + */ + currentIndex: number; + /** + * 增量 + */ + increment: number; + /** + * 是否填充0 + */ + addZero: boolean; + /** + * 填充后长度 + */ + numLength: number; + /** + * 插入位置,front:前缀,backend:后缀,at:位置 + */ + insertType: string; + /** + * 插入的位置 + */ + insertValue: number; + /** + * 忽略拓展名 + */ + ignorePostfix: boolean; + + constructor(data: any) { + this.start = data.start; + this.currentIndex = data.start; + this.increment = data.increment; + this.addZero = data.addZero; + this.numLength = data.numLength; + this.insertType = data.insertType; + this.insertValue = data.insertValue; + this.ignorePostfix = data.ignorePostfix; + } + + deal(file: FileObj): void { + let length = this.currentIndex.toString().length; + let numStr = (this.addZero && this.numLength > length ? "0".repeat(this.numLength - length) : "") + this.currentIndex; + let str = this.ignorePostfix ? file.realName : file.name; + switch (this.insertType) { + case "front": + str = numStr + str; + break; + case "backend": + str = str + numStr; + break; + case "at": + str = str.substring(0, this.insertValue - 1) + numStr + str.substring(this.insertValue - 1); + break; + } + this.currentIndex += this.increment; + + if (this.ignorePostfix) { + file.realName = str; + } else { + file.expandName = path.extname(str); + if (file.expandName.length > 0) { + file.realName = str.substring(0, str.lastIndexOf(".")); + } else { + file.realName = str; + } + } + + file.name = file.realName + file.expandName; + } +} \ No newline at end of file diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..333d249 --- /dev/null +++ b/index.ts @@ -0,0 +1,47 @@ +import koa from "koa"; +import Router from "koa-router"; +import koaBody from "koa-body"; +import * as path from "path"; +import RouterMW from "./middleware/controllerEngine"; + +import config from "./config"; +import handleError from "./middleware/handleError"; +import init from "./middleware/init"; +import SqliteUtil from './util/SqliteHelper'; +import log from './util/LogUtil'; +import { MysqlUtil } from "./util/MysqlHelper"; + + +log.info(config); +const app = new koa(); + +let router = new Router({ + prefix: config.urlPrefix +}); + +app.use(require('koa-static')(path.join(config.rootPath, 'static'))); + +//表单解析 +app.use(koaBody(config.bodyLimit)); +//请求预处理 +app.use(init); +//错误处理 +app.use(handleError); + +app.use(RouterMW(router, path.join(config.rootPath, "dist/api"))); +(async () => { + //初始化sqlite + if (config.sqlite.enable) { + await SqliteUtil.createPool(); + } + //初始化mysql + if (config.mysql.enable) { + await MysqlUtil.createPool(); + } + app.listen(config.port); + log.info(`server listened `, config.port); +})(); + +app.on("error", (error) => { + console.error(error); +}) diff --git a/middleware/controllerEngine.ts b/middleware/controllerEngine.ts new file mode 100644 index 0000000..b90351e --- /dev/null +++ b/middleware/controllerEngine.ts @@ -0,0 +1,50 @@ +import * as fs from 'fs-extra'; +import * as path from 'path'; +import log from '../util/LogUtil'; + +async function addMapping(router, filePath: string) { + let mapping = require(filePath).default; + for (let url in mapping) { + if (url.startsWith('GET ')) { + let temp = url.substring(4); + router.get(temp, mapping[url]); + log.info(`----GET:${temp}`); + } else if (url.startsWith('POST ')) { + let temp = url.substring(5); + router.post(temp, mapping[url]); + log.info(`----POST:${temp}`); + } else if (url.startsWith('PUT ')) { + let temp = url.substring(4); + router.put(temp, mapping[url]); + log.info(`----PUT:${temp}`); + } else if (url.startsWith('DELETE ')) { + let temp = url.substring(7); + router.delete(temp, mapping[url]); + log.info(`----DELETE: ${temp}`); + } else { + log.info(`xxxxx无效路径:${url}`); + } + } +} + +function addControllers(router, filePath) { + let files = fs.readdirSync(filePath).filter(item => item.endsWith('.js')); + for (let index in files) { + let element = files[index]; + let temp = path.join(filePath, element); + let state = fs.statSync(temp); + if (state.isDirectory()) { + addControllers(router, temp); + } else { + if (!temp.endsWith('Helper.js')) { + log.info('\n--开始处理: ' + element + '路由'); + addMapping(router, temp); + } + } + } +} + +export default function engine(router, folder) { + addControllers(router, folder); + return router.routes(); +} diff --git a/middleware/handleError.ts b/middleware/handleError.ts new file mode 100644 index 0000000..79a803c --- /dev/null +++ b/middleware/handleError.ts @@ -0,0 +1,17 @@ +import log from '../util/LogUtil'; + +let f = async (ctx, next) => { + try { + await next(); + } catch (error: any) { + if (error.status != undefined) { + ctx.status = error.status; + } else { + ctx.status = 500; + } + ctx.body = error.message; + log.error(error); + } +} + +export default f; \ No newline at end of file diff --git a/middleware/init.ts b/middleware/init.ts new file mode 100644 index 0000000..e9a2ebd --- /dev/null +++ b/middleware/init.ts @@ -0,0 +1,51 @@ +import config from '../config'; +import ObjectHelper from '../util/ObjectOperate'; + +let doSuccess = (ctx, body) => { + switch (ctx.method) { + case 'GET': + ctx.status = body !== null ? 200 : 204; + ctx.body = body; + break; + case 'POST': + ctx.status = body !== null ? 201 : 204; + ctx.body = body; + break; + case 'PUT': + ctx.status = body !== null ? 200 : 204; + ctx.body = body; + break; + case 'DELETE': + ctx.status = body !== null ? 200 : 204; + ctx.body = body; + break; + } + Object.assign(ctx.allParams, ctx.params); +} + +export default async (ctx, next) => { + //跨域 + ctx.set("Access-Control-Allow-Origin", "*"); + ctx.set("Access-Control-Allow-Headers", "X-Requested-With"); + ctx.set("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS"); + ctx.set("X-Powered-By", ' 3.2.1'); + ctx.set("Content-Type", "application/json;charset=utf-8"); + //合并请求参数到allParams + let objs = new Array(); + if (ctx.method == "POST" || ctx.method == "PUT") { + if (ctx.request.body) { + if (ctx.request.body.fields != undefined && ctx.request.body.files != undefined) { + objs.push(ctx.request.body.fields, ctx.request.body.files); + } else { + objs.push(ctx.request.body); + } + } + } + objs.push(ctx.query); + ctx.allParams = ObjectHelper.combineObject(objs); + + ctx.onSuccess = function (body = null) { + doSuccess(ctx, body); + }; + await next(); +} \ No newline at end of file diff --git a/mysqlSqls/V001_init.sql b/mysqlSqls/V001_init.sql new file mode 100644 index 0000000..0c4a156 --- /dev/null +++ b/mysqlSqls/V001_init.sql @@ -0,0 +1,29 @@ +CREATE TABLE qiezi.host ( + id INT auto_increment NOT NULL, + `key` CHAR(32) NOT NULL COMMENT 'key,用于标识', + secret char(32) NOT NULL COMMENT '密钥', + name varchar(100) NOT NULL COMMENT '网站名', + host varchar(100) NOT NULL COMMENT '网站域名(不含http前缀以及路径)', + pv INT UNSIGNED DEFAULT 0 NOT NULL, + uv varchar(100) DEFAULT 0 NOT NULL, + CONSTRAINT host_pk PRIMARY KEY (id) +) +ENGINE=InnoDB +DEFAULT CHARSET=utf8mb4 +COLLATE=utf8mb4_0900_ai_ci +COMMENT='host表,记录某个站点总的pv,uv数据'; + +CREATE TABLE qiezi.path_date( + id INT auto_increment NOT NULL, + `key` CHAR(32) NOT NULL COMMENT 'key,用于标识', + secret char(32) NOT NULL COMMENT '密钥', + name varchar(100) NOT NULL COMMENT '网站名', + host varchar(100) NOT NULL COMMENT '网站域名(不含http前缀以及路径)', + pv INT UNSIGNED DEFAULT 0 NOT NULL, + uv varchar(100) DEFAULT 0 NOT NULL, + CONSTRAINT host_pk PRIMARY KEY (id) +) +ENGINE=InnoDB +DEFAULT CHARSET=utf8mb4 +COLLATE=utf8mb4_0900_ai_ci +COMMENT='host表,记录某个站点总的pv,uv数据'; diff --git a/package.json b/package.json new file mode 100644 index 0000000..18a334a --- /dev/null +++ b/package.json @@ -0,0 +1,30 @@ +{ + "name": "nas_backup", + "version": "1.0.0", + "description": "文件备份用", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "fxb", + "license": "ISC", + "dependencies": { + "@types/fs-extra": "^5.0.4", + "@types/koa": "^2.0.47", + "@types/node": "^11.13.4", + "axios": "^0.21.1", + "fs-extra": "^7.0.0", + "koa": "^2.13.4", + "koa-body": "^4.2.0", + "koa-router": "^10.1.1", + "koa-static": "^5.0.0", + "koa2-cors": "^2.0.6", + "log4js": "^6.3.0", + "moment": "^2.22.2", + "mysql2": "^2.3.3", + "sqlite": "^4.0.23", + "sqlite3": "^5.0.2", + "uuid": "^3.3.2", + "winston": "^3.1.0" + } +} diff --git a/service/ApplicationRuleService.ts b/service/ApplicationRuleService.ts new file mode 100644 index 0000000..3a648bf --- /dev/null +++ b/service/ApplicationRuleService.ts @@ -0,0 +1,33 @@ +import config from '../config'; +import * as path from 'path'; +import * as fs from 'fs-extra'; +import ApplicationRule from '../entity/dto/ApplicationRule'; +import ApplicationRuleDao from '../dao/ApplicationRuleDao'; + + + +class ApplicationRuleService { + static async saveOrAdd(ruleObj: ApplicationRule): Promise { + ruleObj.updatedDate = Date.now(); + if (!ruleObj.id) { + //说明是新增 + ruleObj.createdDate = Date.now(); + ruleObj.id = await ApplicationRuleDao.addOne(ruleObj); + } else { + //说明是修改 + await ApplicationRuleDao.updateOne(ruleObj); + } + return ruleObj; + } + + static async getAll(): Promise> { + return await ApplicationRuleDao.getAll(); + } + + static async deleteById(id: number): Promise { + await ApplicationRuleDao.delete(id); + } + +} + +export default ApplicationRuleService; diff --git a/service/FileService.ts b/service/FileService.ts new file mode 100644 index 0000000..4148d74 --- /dev/null +++ b/service/FileService.ts @@ -0,0 +1,58 @@ +import config from '../config'; +import * as path from 'path'; +import * as fs from 'fs-extra'; + +import ProcessHelper from '../util/ProcesHelper'; +import FileObj from '../vo/FileObj'; + +class FileService { + static async readPath(pathStr: string, showHidden: boolean): Promise> { + pathStr = decodeURIComponent(pathStr); + let fileList = new Array(); + if (pathStr.trim().length == 0) { + //获取根目录路径 + if (config.isWindows) { + //windows下 + let std: string = (await ProcessHelper.exec('wmic logicaldisk get caption')).replace('Caption', ''); + fileList = std + .split('\r\n') + .filter((item) => item.trim().length > 0) + .map((item) => item.trim()); + } else { + //linux下 + pathStr = '/'; + fileList = await fs.readdir(pathStr); + } + } else { + fileList = await fs.readdir(pathStr); + } + let folderList: Array = new Array(); + let files: Array = new Array(); + for (let index in fileList) { + try { + let stat = await fs.stat(path.join(pathStr, fileList[index])); + if (fileList[index].startsWith('.')) { + if (showHidden) { + (stat.isDirectory() ? folderList : files).push( + new FileObj(fileList[index], pathStr, stat.isDirectory(), stat.birthtime.getTime(), stat.mtime.getTime()), + ); + } + } else { + (stat.isDirectory() ? folderList : files).push( + new FileObj(fileList[index], pathStr, stat.isDirectory(), stat.birthtime.getTime(), stat.mtime.getTime()), + ); + } + } catch (e) { + console.error(e); + } + } + folderList.sort((a, b) => a.name.localeCompare(b.name)).push(...files.sort((a, b) => a.name.localeCompare(b.name))); + return folderList; + } + + static async checkExist(pathStr: string) { + return await fs.pathExists(pathStr); + } +} + +export default FileService; diff --git a/service/RenamerService.ts b/service/RenamerService.ts new file mode 100644 index 0000000..b6457b6 --- /dev/null +++ b/service/RenamerService.ts @@ -0,0 +1,41 @@ +import config from '../config'; +import * as path from 'path'; +import * as fs from 'fs-extra'; + +import FileObj from '../vo/FileObj'; +import RuleObj from '../vo/RuleObj'; +import DeleteRule from '../vo/rules/DeleteRule'; +import RuleInterface from '../vo/rules/RuleInterface'; + + +class RenamerService { + static async preview(fileList: Array, ruleList: Array): Promise> { + let ruleObjs = ruleList.map(item => new RuleObj(item)); + let newNameSet: Set = new Set(); + for (let i in fileList) { + let obj = fileList[i]; + ruleObjs.forEach(item => (item.data as RuleInterface).deal(obj)); + if (newNameSet.has(obj.name)) { + obj.errorMessage = "重名"; + } + newNameSet.add(obj.name); + } + return fileList; + } + + static async rename(fileList: Array, changedFileList: Array) { + for (let i in fileList) { + let old = fileList[i]; + let oldPath = path.join(fileList[i].path, fileList[i].name); + let newPath = path.join(changedFileList[i].path, changedFileList[i].name); + if ((await fs.pathExists(newPath))) { + throw new Error("此路径已存在:" + newPath); + } + await fs.rename(oldPath, newPath); + } + } + + +} + +export default RenamerService; diff --git a/static/.gitkeep b/static/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..7b3f7c9 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2017", + "noImplicitAny": false, + "module": "commonjs", + "sourceMap": true, + "outDir": "./dist", + "baseUrl":".", + "rootDir": "./", + "watch": false, + "strict": true, + "strictNullChecks": false, + "esModuleInterop": true + } +} \ No newline at end of file diff --git a/util/ErrorHelper.ts b/util/ErrorHelper.ts new file mode 100644 index 0000000..cb8240a --- /dev/null +++ b/util/ErrorHelper.ts @@ -0,0 +1,32 @@ +class ErrorHelper { + /** + * 返回一个自定义错误 + * @param {String} message + * @param {Number} status + */ + static newError(message, status) { + return getError(message, status); + } + + static Error403(message){ + return getError(message,403); + } + static Error404(message){ + return getError(message,404); + } + static Error406(message){ + return getError(message,406); + } + static Error400(message){ + return getError(message,400); + } + +} + +let getError = (message, status) => { + let error = new Error(message); + error['status'] = status; + return error; +} + +export default ErrorHelper; \ No newline at end of file diff --git a/util/LogUtil.ts b/util/LogUtil.ts new file mode 100644 index 0000000..b57116b --- /dev/null +++ b/util/LogUtil.ts @@ -0,0 +1,9 @@ +import { getLogger, configure } from "log4js"; +configure({ + appenders: { cheese: { type: "console" } }, + categories: { default: { appenders: ["cheese"], level: "info" } } +}); +const logger = getLogger(); +logger.level = "debug"; + +export default logger; \ No newline at end of file diff --git a/util/MysqlHelper.ts b/util/MysqlHelper.ts new file mode 100644 index 0000000..01bb8a4 --- /dev/null +++ b/util/MysqlHelper.ts @@ -0,0 +1,108 @@ +import mysql from "mysql2/promise"; +import config from '../config'; +import * as fs from "fs-extra"; +import * as path from 'path'; +import log from '../util/LogUtil'; + +const HISTORY_NAME = "mysqlHistory.json"; + +interface Res { + rows: any; + fields: mysql.FieldPacket; +} + +class MysqlUtil { + public static pool: mysql.Pool = null; + + static async createPool() { + MysqlUtil.pool = await mysql.createPool(config.mysql.connection); + let basePath = path.join(config.rootPath, config.mysql.sqlFolder); + let hisPath = path.join(config.rootPath, HISTORY_NAME); + let history: Array; + if (fs.existsSync(hisPath)) { + history = JSON.parse(await fs.readFile(hisPath, "utf-8")); + } else { + history = new Array(); + } + //执行数据库 + let files = (await fs.readdir(basePath)).sort((a, b) => a.localeCompare(b)).filter(item => !(item === HISTORY_NAME)); + let error = null; + for (let i = 0; i < files.length; i++) { + if (history.indexOf(files[i]) > -1) { + log.info("sql无需重复执行:", files[i]); + continue; + } + let sqlLines = (await fs.readFile(path.join(basePath, files[i]), 'utf-8')).split(/[\r\n]/g); + try { + let sql = ""; + for (let j = 0; j < sqlLines.length; j++) { + sql = sql + " " + sqlLines[j]; + if (sqlLines[j].endsWith(";")) { + await MysqlUtil.pool.execute(sql); + sql = ""; + } + } + log.info("sql执行成功:", files[i]); + history.push(files[i]); + } catch (err) { + error = err; + break; + } + } + await fs.writeFile(hisPath, JSON.stringify(history)); + if (error != null) { + throw error; + } + } + + static async getRows(sql: string, params: Array, connection: mysql.PoolConnection = null): Promise> { + return (await MysqlUtil.execute(sql, params, connection)).rows; + } + + + static async getRow(sql: string, params: Array, connection: mysql.PoolConnection = null): Promise { + let rows = (await MysqlUtil.execute(sql, params, connection)).rows; + return rows.length > 0 ? rows[0] : null; + } + + static async getSingle(sql: string, params: Array, connection: mysql.PoolConnection = null): Promise { + let rows = (await MysqlUtil.execute(sql, params, connection)).rows; + if (rows.length == 0) { + return null; + } + let row = rows[0]; + return row[Object.keys(row)[0]]; + } + + + + static async execute(sql: string, params: Array, connection: mysql.PoolConnection = null): Promise { + let res: any = {}; + if (connection == null) { + let [rows, fields] = await MysqlUtil.pool.query(sql, params); + res['rows'] = fields === undefined ? null : rows; + res['fields'] = fields === undefined ? rows : fields; + } else { + let [rows, fields] = await connection.query(sql, params); + res['rows'] = rows; + res['fields'] = fields; + } + return res; + } + + + static async test() { + let connection = await MysqlUtil.pool.getConnection(); + connection.beginTransaction(); + connection.query(`insert into url value(6,"GET","asd","public")`); + connection.query(`insert into url value(7,"GET","asd","public")`); + await connection.commit(); + connection.release(); + } +} + +export { + MysqlUtil, + Res, + mysql +} \ No newline at end of file diff --git a/util/NumberUtil.ts b/util/NumberUtil.ts new file mode 100644 index 0000000..1d3f07d --- /dev/null +++ b/util/NumberUtil.ts @@ -0,0 +1,8 @@ + +class NumberUtil { + static getRandom(min: number, max: number): number { + return Math.floor((Math.random() * (max - min + 1) + min)); + } +} + +export default NumberUtil; \ No newline at end of file diff --git a/util/ObjectOperate.ts b/util/ObjectOperate.ts new file mode 100644 index 0000000..8287ed2 --- /dev/null +++ b/util/ObjectOperate.ts @@ -0,0 +1,18 @@ +/* + 合并node对象,对于相同的属性后面覆盖前面 +*/ +class ObjectOperation { + static combineObject(...objs) { + if (objs.length == 1 && objs[0] instanceof Array) { + objs = objs[0]; + } + let sum = {}; + let length = objs.length; + for (let i = 0; i < length; i++) { + sum = Object.assign(sum,objs[i]); + } + return sum; + } +} + +export default ObjectOperation \ No newline at end of file diff --git a/util/ProcesHelper.ts b/util/ProcesHelper.ts new file mode 100644 index 0000000..16d0254 --- /dev/null +++ b/util/ProcesHelper.ts @@ -0,0 +1,24 @@ +import * as childPrecess from 'child_process'; + +class ProcessHelper { + static exec(cmd): Promise { + return new Promise((resolve, reject) => { + childPrecess.exec(cmd, (error, stdout, stderr) => { + if (error) { + reject(error); + } if (stderr) { + reject(stderr); + } else { + resolve(stdout) + } + }) + }) + } +} + +// (async()=>{ +// let res= await ProcessHelper.exec('cd /d e://workspace&&dir'); +// console.log(res); +// })() + +export default ProcessHelper \ No newline at end of file diff --git a/util/SqliteHelper.ts b/util/SqliteHelper.ts new file mode 100644 index 0000000..801555d --- /dev/null +++ b/util/SqliteHelper.ts @@ -0,0 +1,64 @@ +import sqlite3 from 'sqlite3'; +import { open, Database } from 'sqlite'; +import config from '../config'; +import * as fs from "fs-extra"; +import * as path from 'path'; +import log from './LogUtil'; + +const HISTORY_NAME = "sqliteHistory.json"; + + +class SqliteHelper { + public static pool: Database = null; + + static async createPool() { + let fullPath = path.join(config.rootPath, config.sqlite.filePath); + let dataFolder = path.dirname(fullPath); + if (!fs.existsSync(dataFolder)) { + fs.mkdir(dataFolder); + } + SqliteHelper.pool = await open({ + filename: fullPath, + driver: sqlite3.Database + }); + let sqlFolder = path.join(config.rootPath, config.sqlite.sqlFolder); + let hisPath = path.join(config.rootPath, HISTORY_NAME); + let history: Array; + if (fs.existsSync(hisPath)) { + history = JSON.parse(await fs.readFile(hisPath, "utf-8")); + } else { + history = new Array(); + } + //执行数据库 + let files = (await fs.readdir(sqlFolder)).sort((a, b) => a.localeCompare(b)).filter(item => !(item === HISTORY_NAME)); + let error = null; + for (let i = 0; i < files.length; i++) { + if (history.indexOf(files[i]) > -1) { + log.info("sql无需重复执行:", files[i]); + continue; + } + let sqlLines = (await fs.readFile(path.join(sqlFolder, files[i]), 'utf-8')).split(/[\r\n]/g).map(item => item.trim()).filter(item => !item.startsWith("--")); + try { + let sql = ""; + for (let j = 0; j < sqlLines.length; j++) { + sql = sql + sqlLines[j]; + if (sqlLines[j].endsWith(";")) { + await SqliteHelper.pool.run(sql); + sql = ""; + } + } + log.info("sql执行成功:", files[i]); + history.push(files[i]); + } catch (err) { + error = err; + break; + } + } + await fs.writeFile(hisPath, JSON.stringify(history)); + if (error != null) { + throw error; + } + } +} + +export default SqliteHelper; diff --git a/util/TimeUtil.ts b/util/TimeUtil.ts new file mode 100644 index 0000000..6e1d032 --- /dev/null +++ b/util/TimeUtil.ts @@ -0,0 +1,22 @@ +import moment from 'moment'; +class TimeUtil { + /** + * 获取今天的零点 + */ + static getZeroTime(): Date { + return moment() + .millisecond(0) + .second(0) + .minute(0) + .hour(0) + .toDate(); + } + + static async sleep(duration: number): Promise { + return new Promise((resolve, reject) => { + setTimeout(() => resolve(), duration); + }); + } +} + +export default TimeUtil; diff --git a/util/pathUtil.ts b/util/pathUtil.ts new file mode 100644 index 0000000..428e1a4 --- /dev/null +++ b/util/pathUtil.ts @@ -0,0 +1,14 @@ +import path, { dirname } from 'path' + +class pathUtil { + static getPath(pathStr) { + return path.resolve(pathUtil.getRootPath(), pathStr); + } + + static getRootPath() { + return path.resolve(__dirname, '..'); + } +} + + +export default pathUtil \ No newline at end of file