Merge pull request 'electron' (#48) from electron into main

Reviewed-on: #48
This commit is contained in:
fanxb 2024-01-07 15:35:43 +08:00
commit 2e50b9653a
21 changed files with 6695 additions and 2200 deletions

View File

@ -2,7 +2,11 @@
![预览图](https://s3.fleyx.com/picbed/2022/11/18386180128d01eb1a59b8eacf652895.png)
renamer 的开源实现版本BS 应用,支持 arm/x86 部署使用
已打包镜像到 dockerhub 中:[hub.docker.com/r/fleyx/open-renamer](https://hub.docker.com/r/fleyx/open-renamer)
renamer 的开源实现版本BS 应用,支持 arm/x86 部署使用,两种使用方式:
1. 部署容器到 nas
已打包到 dockerhub 中:[hub.docker.com/r/fleyx/open-renamer](https://hub.docker.com/r/fleyx/open-renamer)
2. 下载桌面应用使用,目前仅支持 windows后续计划支持 mac,linux[下载地址](https://github.com/FleyX/open-renamer/releases/latest)
[点击查看参考文档](https://blog.fleyx.com/blog/detail/20221130)

6
electron/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
node_modules
dist
openRenamerBackend
build
.idea
*.exe

1
electron/.npmrc Normal file
View File

@ -0,0 +1 @@
electron_mirror=https://npmmirror.com/mirrors/electron/

11
electron/build.sh Normal file
View File

@ -0,0 +1,11 @@
cd ../openRenamerFront
yarn
npm run build
cd ../openRenamerBackend
yarn
tsc
rm -rf ./static/js
rm -rf ./static/css
cp -r ../openRenamerFront/dist/* ./static
cd ../electron
npm run build

129
electron/main.js Normal file
View File

@ -0,0 +1,129 @@
// main.js
// 控制应用生命周期和创建原生浏览器窗口的模组
const {app, BrowserWindow, Menu} = require('electron')
const path = require('path')
const fs = require('fs');
const {spawn} = require('child_process');
const net = require('net');
const log = require('electron-log');
const userHome = process.env.HOME || process.env.USERPROFILE;
const dataPath = path.join(userHome, "openRenamer");
log.transports.file.resolvePathFn = () => path.join(dataPath, 'logs/main.log');
async function createWindow() {
// 隐藏菜单栏
Menu.setApplicationMenu(null)
// 创建浏览器窗口
const win = new BrowserWindow({
//width: 800, //窗口宽度,单位像素. 默认是 800
//height: 600, //窗口高度,单位像素. 默认是 600
icon: './logo.ico', // 设置窗口左上角的图标
show: false, //窗口创建的时候是否显示. 默认为 true
webPreferences: {
nodeIntegration: true, // 是否完整支持node。默认为 true
preload: path.join(__dirname, 'preload.js') //界面的其它脚本运行之前预先加载一个指定脚本。
}
})
// 下面这两行代码配合上面 new BrowserWindow 里面的 show: false可以实现打开时窗口最大化
win.maximize()
win.show()
log.info(__dirname);
let port = await startBackend()
// 并且为你的应用加载index.html
// win.loadFile('./dist/index.html')
log.info("backend service started")
win.loadURL(`http://localhost:` + port);
// win.webContents.openDevTools()
}
// Electron会在初始化完成并且准备好创建浏览器窗口时调用这个方法
// 部分 API 在 ready 事件触发后才能使用。
app.whenReady().then(createWindow)
// 当所有窗口都被关闭后退出
app.on('windows-all-closed', () => {
// 在 macOS 上,除非用户用 Cmd + Q 确定地退出,
// 否则绝大部分应用及其菜单栏会保持激活。
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
// 在macOS上当单击dock图标并且没有其他窗口打开时
// 通常在应用程序中重新创建一个窗口。
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
/**
* 启动后台服务
* @returns {Promise<number>}
*/
async function startBackend() {
let port = 51000;
while (true) {
let ok = await checkPort(port);
if (ok) {
break;
}
port = port + 1;
}
log.info("start check folder exist", __dirname, __filename)
let exist = fs.existsSync("openRenamerBackend");
const childProcess = spawn('node', [(exist ? '' : '../') + 'openRenamerBackend/dist/index.js'], {
env: {
"PORT": port,
"DATA_PATH": dataPath
}
});
childProcess.stdout.on('data', (data) => {
log.info(`stdout: ${data}`);
});
childProcess.stderr.on('data', (data) => {
log.error(`stderr: ${data}`);
});
childProcess.on('close', (code) => {
log.info(`child process exited with code ${code}`);
});
log.info("check service start");
while (true) {
await sleep(100);
let success = !(await checkPort(port));
if (success) {
log.info("service start");
break;
}
}
return port;
}
/**
* 判断端口是否可用
* @param port
* @returns {Promise<unknown>}
*/
function checkPort(port) {
return new Promise((resolve, reject) => {
let server = net.createServer().listen(port);
server.on("listening", function () {
server.close();
resolve(true);
})
server.on("error", function (err) {
console.error(err);
resolve(false);
})
})
}
function sleep(time) {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(), time);
})
}

71
electron/package.json Normal file
View File

@ -0,0 +1,71 @@
{
"name": "electron",
"version": "1.0.0",
"description": "",
"main": "main.js",
"scripts": {
"start": "electron .",
"test": "echo \"Error: no test specified\" && exit 1",
"build": "electron-builder --win --x64"
},
"author": "",
"license": "ISC",
"devDependencies": {
"electron": "^28.0.0",
"electron-builder": "^24.9.1"
},
"build": {
"productName": "openRenamer",
"appId": "openRenamer.app",
"directories": {
"output": "build"
},
"files": [
"main.js",
"preload.js"
],
"extraFiles": [
{
"from": "../openRenamerBackend",
"to": "openRenamerBackend"
}
],
"copyright": "open-renamer",
"nsis": {
"oneClick": false,
"allowElevation": true,
"allowToChangeInstallationDirectory": true,
"installerIcon": "./renamer.ico",
"uninstallerIcon": "./renamer.ico",
"installerHeaderIcon": "./renamer.ico",
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"shortcutName": "openRenamer"
},
"win": {
"icon": "./renamer.ico",
"target": [
"nsis",
"zip"
],
"extraFiles": [
{
"from": "windows/node.exe",
"to": "node.exe"
}
]
},
"mac": {
"target": [
"dmg",
"zip"
]
},
"linux": {
"icon": "build/icons"
}
},
"dependencies": {
"electron-log": "^5.0.1"
}
}

12
electron/preload.js Normal file
View File

@ -0,0 +1,12 @@
// preload.js
// 所有Node.js API都可以在预加载过程中使用。
// 它拥有与Chrome扩展一样的沙盒。
window.addEventListener('DOMContentLoaded', () => {
const replaceText = (selector, text) => {
const element = document.getElementById(selector)
if (element) element.innerText = text
}
for (const dependency of ['chrome', 'node', 'electron']) {
replaceText(`${dependency}-version`, process.versions[dependency])
}
})

1
electron/readme.md Normal file
View File

@ -0,0 +1 @@
需要下载windows版的node.js压缩包将其中的node.exe 放到windowes目录下

BIN
electron/renamer.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

1705
electron/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,12 @@
import * as path from 'path';
import * as process from "process";
//后台所在绝对路径
const rootPath = path.resolve(__dirname, '..');
let config = {
rootPath,
dataPath: process.env.DATA_PATH ? process.env.DATA_PATH : path.join(rootPath, 'data'),
port: process.env.PORT ? parseInt(process.env.PORT) : 8089,
token: process.env.TOKEN ? process.env.TOKEN : null,
urlPrefix: '/openRenamer/api',

View File

@ -10,6 +10,7 @@ import init from "./middleware/init";
import SqliteUtil from './util/SqliteHelper';
import log from './util/LogUtil';
import {updateQbInfo} from './util/QbApiUtil';
import TimeUtil from "./util/TimeUtil";
console.log(config);

View File

@ -9,22 +9,20 @@
"author": "fxb",
"license": "ISC",
"dependencies": {
"@types/fs-extra": "^5.0.4",
"@types/koa": "^2.0.47",
"@types/node": "^11.13.4",
"axios": "^0.21.4",
"fs-extra": "^7.0.0",
"koa": "^2.5.3",
"koa-body": "^4.0.4",
"koa-router": "^7.4.0",
"koa-static": "^5.0.0",
"koa2-cors": "^2.0.6",
"log4js": "^6.3.0",
"moment": "^2.22.2",
"mysql2": "^2.2.5",
"sqlite": "^4.0.23",
"sqlite3": "^5.0.2",
"uuid": "^3.3.2",
"winston": "^3.1.0"
"@types/fs-extra": "5.0.4",
"@types/koa": "2.13.12",
"@types/node": "11.13.4",
"axios": "0.21.4",
"fs-extra": "7.0.0",
"koa": "2.5.3",
"koa-body": "4.0.4",
"koa-router": "7.4.0",
"koa-static": "5.0.0",
"koa2-cors": "2.0.6",
"log4js": "6.3.0",
"moment": "2.22.2",
"sqlite": "4.0.23",
"sqlite3": "5.0.2",
"uuid": "3.3.2"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,108 +0,0 @@
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 = "history.json";
interface Res {
rows: any;
fields: mysql.FieldPacket;
}
class MysqlUtil {
public static pool: mysql.Pool = null;
static async createPool(mysqlConfig: any) {
MysqlUtil.pool = await mysql.createPool(mysqlConfig);
let basePath = path.join(config.rootPath, "sqls");
let hisPath = path.join(basePath, HISTORY_NAME);
let history: Array<string>;
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<any>, connection: mysql.PoolConnection = null): Promise<Array<any>> {
return (await MysqlUtil.execute(sql, params, connection)).rows;
}
static async getRow(sql: string, params: Array<any>, connection: mysql.PoolConnection = null): Promise<any> {
let rows = (await MysqlUtil.execute(sql, params, connection)).rows;
return rows.length > 0 ? rows[0] : null;
}
static async getSingle(sql: string, params: Array<any>, connection: mysql.PoolConnection = null): Promise<any> {
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<any>, connection: mysql.PoolConnection = null): Promise<Res> {
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
}

View File

@ -1,5 +1,5 @@
import sqlite3 from 'sqlite3';
import { open, Database } from 'sqlite';
import {open, Database} from 'sqlite';
import config from '../config';
import * as fs from "fs-extra";
import * as path from 'path';
@ -12,9 +12,9 @@ class SqliteHelper {
public static pool: Database = null;
static async createPool() {
let dataFolder = path.join(config.rootPath, "data");
let dataFolder = config.dataPath;
if (!fs.existsSync(dataFolder)) {
fs.mkdir(dataFolder);
await fs.mkdir(dataFolder);
}
SqliteHelper.pool = await open({
filename: path.join(dataFolder, "database.db"),
@ -26,7 +26,7 @@ class SqliteHelper {
if (fs.existsSync(hisPath)) {
history = JSON.parse(await fs.readFile(hisPath, "utf-8"));
} else {
history = new Array();
history = [];
}
//执行数据库
let files = (await fs.readdir(basePath)).sort((a, b) => a.localeCompare(b)).filter(item => !(item === HISTORY_NAME));

View File

@ -8,21 +8,21 @@
"lint": "vue-cli-service lint"
},
"dependencies": {
"@element-plus/icons-vue": "^2.0.10",
"axios": "^0.21.1",
"core-js": "^3.6.5",
"dayjs": "^1.10.7",
"element-plus": "^2.2.25",
"vue": "^3.2.45",
"vue-router": "^4.0.0-0"
"@element-plus/icons-vue": "2.0.10",
"axios": "1.6.0",
"core-js": "3.35.0",
"dayjs": "1.10.7",
"element-plus": "2.2.25",
"vue": "3.2.45",
"vue-router": "4.2.5"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~5.0.8",
"@vue/cli-plugin-router": "~5.0.8",
"@vue/cli-service": "~5.0.8",
"@vue/compiler-sfc": "^3.0.0",
"less": "^3.0.4",
"less-loader": "^5.0.0",
"prettier": "^2.2.1"
"@vue/cli-plugin-babel": "5.0.8",
"@vue/cli-plugin-router": "5.0.8",
"@vue/cli-service": "5.0.8",
"@vue/compiler-sfc": "3.0.0",
"less": "3.0.4",
"less-loader": "5.0.0",
"prettier": "2.2.1"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -29,8 +29,8 @@
</el-tooltip>
&nbsp;&nbsp;
</template>
开源地址:<a href="https://github.com/FleyX/open-renamer">open-renamer</a>
&nbsp;&nbsp;<a href="https://github.com/FleyX/open-renamer/issues">反馈</a>
开源地址:<a href="https://github.com/FleyX/open-renamer" target="_blank">open-renamer</a>
&nbsp;&nbsp;<a href="https://github.com/FleyX/open-renamer/issues" target="_blank">反馈</a>
</div>
</div>
</template>
@ -49,6 +49,17 @@ export default {
};
},
async beforeCreate() {
console.log("beforeCreate");
let queryMap = {};
location.search.substring(1).split("&").forEach(item => {
let arr = item.split("=");
queryMap[arr[0]] = arr[1];
})
if (queryMap.port) {
window.baseUrl = "http://localhost:" + queryMap.port;
} else {
window.baseUrl = "";
}
window.token = localStorage.getItem("token");
window.isWindows = await httpUtil.get("/file/isWindows");
},

View File

@ -14,7 +14,7 @@ import router from '../router/index';
async function request(url, method, params, body, isForm) {
let options = {
url,
baseURL: '/openRenamer/api',
baseURL: window.baseUrl + '/openRenamer/api',
method,
params,
headers: {token: window.token}

View File

@ -1,5 +1,6 @@
module.exports = {
devServer: {
proxy: "http://localhost:8089",
},
devServer: {
proxy: "http://localhost:8089",
},
publicPath: "./"
};