diff --git a/electron/controller/cross.js b/electron/controller/cross.js index 92a0e82..63fbc92 100644 --- a/electron/controller/cross.js +++ b/electron/controller/cross.js @@ -10,14 +10,27 @@ class CrossController { * View process service information */ info() { + const pids = Cross.getPids(); + Log.info('cross pids:', pids); + let num = 1; + pids.forEach(pid => { + let entity = Cross.getProc(pid); + Log.info(`server-${num} name:${entity.name}`); + Log.info(`server-${num} config:`, entity.config); + num++; + }) + + return 'hello electron-egg'; } /** * Get service url */ async getUrl(args) { - + const { name } = args; + const serverUrl = Cross.getUrl(name); + return serverUrl; } /** @@ -25,21 +38,51 @@ class CrossController { * By default (modifiable), killing the process will exit the electron application. */ async killServer(args) { + const { type, name } = args; + if (type == 'all') { + Cross.killAll(); + } else { + Cross.killByName(name); + } + return; } /** * create service */ async createServer(args) { + const { program } = args; + if (program == 'go') { + Services.get('cross').createGoServer(); + } else if (program == 'java') { + Services.get('cross').createJavaServer(); + } else if (program == 'python') { + Services.get('cross').createPythonServer(); + } + return; } /** * Access the api for the cross service */ async requestApi(args) { + const { name, urlPath, params} = args; + const hc = new HttpClient(); + const serverUrl = Cross.getUrl(name); + console.log('Server Url:', serverUrl); + const apiHello = serverUrl + urlPath; + const options = { + method: 'GET', + data: params || {}, + dataType: 'json', + timeout: 1000, + }; + const result = await hc.request(apiHello, options); + + return result.data; } } diff --git a/electron/controller/effect.js b/electron/controller/effect.js index a598253..f4c692a 100644 --- a/electron/controller/effect.js +++ b/electron/controller/effect.js @@ -1,31 +1,65 @@ 'use strict'; +const { dialog } = require('electron'); +const _ = require('lodash'); +const { getMainWindow } = require('ee-core/electron/window'); + /** - * 特效 - 功能demo + * effect - demo * @class */ class EffectController { /** - * 选择文件 + * select file */ selectFile() { + const filePaths = dialog.showOpenDialogSync({ + properties: ['openFile'] + }); + if (_.isEmpty(filePaths)) { + return null + } + + return filePaths[0]; } /** * login window */ loginWindow(args) { - + const { width, height } = args; + const win = getMainWindow(); + + const size = { + width: width || 400, + height: height || 300 + } + win.setSize(size.width, size.height); + win.setResizable(true); + win.center(); + win.show(); + win.focus(); } /** * restore window */ restoreWindow(args) { + const { width, height } = args; + const win = getMainWindow(); - } + const size = { + width: width || 980, + height: height || 650 + } + win.setSize(size.width, size.height); + win.setResizable(true); + win.center(); + win.show(); + win.focus(); + } } EffectController.toString = () => '[class EffectController]'; diff --git a/electron/controller/framework.js b/electron/controller/framework.js index 7c2895e..5384a9f 100644 --- a/electron/controller/framework.js +++ b/electron/controller/framework.js @@ -1,17 +1,277 @@ 'use strict'; +const dayjs = require('dayjs'); +const path = require('path'); +const fs = require('fs'); +const { exec } = require('child_process'); +const { app: electronApp, shell } = require('electron'); +const { getExtraResourcesDir } = require('ee-core/ps'); +const { logger } = require('ee-core/log'); +const { getConfig } = require('ee-core/config'); + /** - * example + * framework - demo * @class */ class FrameworkController { /** - * test + * 所有方法接收两个参数 + * @param args 前端传的参数 + * @param event - ipc通信时才有值。详情见:控制器文档 */ - async test () { - return 'hello electron-egg'; + + /** + * sqlite数据库操作 + */ + async sqlitedbOperation(args) { + const { action, info, delete_name, update_name, update_age, search_age, data_dir } = args; + + const data = { + action, + result: null, + all_list: [], + code: 0 + }; + + try { + // test + Services.get('database.sqlitedb').getDataDir(); + } catch (err) { + console.log(err); + data.code = -1; + return data; + } + + switch (action) { + case 'add' : + data.result = await Services.get('database.sqlitedb').addTestDataSqlite(info);; + break; + case 'del' : + data.result = await Services.get('database.sqlitedb').delTestDataSqlite(delete_name);; + break; + case 'update' : + data.result = await Services.get('database.sqlitedb').updateTestDataSqlite(update_name, update_age); + break; + case 'get' : + data.result = await Services.get('database.sqlitedb').getTestDataSqlite(search_age); + break; + case 'getDataDir' : + data.result = await Services.get('database.sqlitedb').getDataDir(); + break; + case 'setDataDir' : + data.result = await Services.get('database.sqlitedb').setCustomDataDir(data_dir); + break; + } + + data.all_list = await Services.get('database.sqlitedb').getAllTestDataSqlite(); + + return data; + } + + /** + * 调用其它程序(exe、bash等可执行程序) + * + */ + openSoftware(softName) { + if (!softName) { + return false; + } + + let softwarePath = path.join(getExtraResourcesDir(), softName); + logger.info('[openSoftware] softwarePath:', softwarePath); + + // 检查程序是否存在 + if (!fs.existsSync(softwarePath)) { + return false; + } + // 命令行字符串 并 执行, start 命令后面的路径要加双引号 + let cmdStr = `start "${softwarePath}"`; + exec(cmdStr); + + // 方法二 + // 推荐使用cross模块 + + return true; + } + + /** + * 检查是否有新版本 + */ + checkForUpdater() { + Addon.get('autoUpdater').checkUpdate(); + return; } + + /** + * 下载新版本 + */ + downloadApp() { + Addon.get('autoUpdater').download(); + return; + } + + /** + * 检测http服务是否开启 + */ + async checkHttpServer() { + const { httpServer } = getConfig; + const url = httpServer.protocol + httpServer.host + ':' + httpServer.port; + + const data = { + enable: httpServer.enable, + server: url + } + return data; + } + + /** + * [todo] 一个http请求访问此方法 + */ + async doHttpRequest() { + const { CoreApp } = EE; + // http方法 + const method = CoreApp.request.method; + // http get 参数 + let params = CoreApp.request.query; + params = (params instanceof Object) ? params : JSON.parse(JSON.stringify(params)); + // http post 参数 + const body = CoreApp.request.body; + + const httpInfo = { + method, + params, + body + } + Log.info('httpInfo:', httpInfo); + + if (!body.id) { + return false; + } + const dir = electronApp.getPath(body.id); + shell.openPath(dir); + + return true; + } + + /** + * 一个socket io请求访问此方法 + */ + async doSocketRequest(args) { + const { id } = args; + if (!id) { + return false; + } + const dir = electronApp.getPath(id); + shell.openPath(dir); + + return true; + } + + /** + * 异步消息类型 + */ + async ipcInvokeMsg(args, event) { + let timeNow = dayjs().format('YYYY-MM-DD HH:mm:ss'); + const data = args + ' - ' + timeNow; + + return data; + } + + /** + * 同步消息类型 + */ + async ipcSendSyncMsg(args) { + let timeNow = dayjs().format('YYYY-MM-DD HH:mm:ss'); + const data = args + ' - ' + timeNow; + + return data; + } + + /** + * 双向异步通信 + */ + async ipcSendMsg(args, event) { + const { type, content } = args; + const data = await Services.get('framework').bothWayMessage(type, content, event); + + return data; + } + + /** + * 任务 + */ + someJob(args, event) { + let jobId = args.id; + let action = args.action; + + let result; + switch (action) { + case 'create': + result = Services.get('framework').doJob(jobId, action, event); + break; + case 'close': + Services.get('framework').doJob(jobId, action, event); + break; + case 'pause': + Services.get('framework').doJob(jobId, action, event); + break; + case 'resume': + Services.get('framework').doJob(jobId, action, event); + break; + default: + } + + let data = { + jobId, + action, + result + } + return data; + } + + /** + * 创建任务池 + */ + async createPool(args, event) { + let num = args.number; + Services.get('framework').doCreatePool(num, event); + + // test monitor + Services.get('framework').monitorJob(); + + return; + } + + /** + * 通过进程池执行任务 + */ + someJobByPool(args, event) { + let jobId = args.id; + let action = args.action; + + let result; + switch (action) { + case 'run': + result = Services.get('framework').doJobByPool(jobId, action, event); + break; + default: + } + + let data = { + jobId, + action, + result + } + return data; + } + + /** + * 测试接口 + */ + hello(args) { + Log.info('hello ', args); + } } FrameworkController.toString = () => '[class FrameworkController]'; diff --git a/electron/controller/hardware.js b/electron/controller/hardware.js new file mode 100644 index 0000000..547042c --- /dev/null +++ b/electron/controller/hardware.js @@ -0,0 +1,66 @@ +'use strict'; + +const path = require('path'); +const { getBaseDir } = require('ee-core/ps'); +const { getMainWindow } = require('ee-core/electron/window'); + +/** + * 硬件设备 - 功能demo + * @class + */ +class HardwareController { + + /** + * 获取打印机列表 + */ + async getPrinterList () { + + //主线程获取打印机列表 + const win = getMainWindow(); + const list = await win.webContents.getPrintersAsync(); + + return list; + } + + /** + * 打印 + */ + print (args, event) { + const { view, deviceName } = args; + let content = null; + if (view.type == 'html') { + content = path.join('file://', getBaseDir(), view.content) + } else { + content = view.content; + } + + let opt = { + title: 'printer window', + x: 10, + y: 10, + width: 980, + height: 650 + } + const name = 'window-printer'; + const printWindow = Addon.get('window').create(name, opt); + + printWindow.loadURL(content); + printWindow.webContents.once('did-finish-load', () => { + // 页面完全加载完成后,开始打印 + printWindow.webContents.print({ + silent: false, // 显示打印对话框 + printBackground: true, + deviceName, + }, (success, failureReason) => { + const channel = 'controller.hardware.printStatus'; + event.reply(`${channel}`, { success, failureReason }); + printWindow.close(); + }); + }); + + return true; + } +} + +HardwareController.toString = () => '[class HardwareController]'; +module.exports = HardwareController; \ No newline at end of file diff --git a/electron/controller/os.js b/electron/controller/os.js index 1b42146..f13566f 100644 --- a/electron/controller/os.js +++ b/electron/controller/os.js @@ -1,5 +1,13 @@ 'use strict'; +const _ = require('lodash'); +const path = require('path'); +const { + app: electronApp, dialog, shell, Notification, + powerMonitor, screen, nativeTheme +} = require('electron'); +const { isProd, getBaseDir } = require('ee-core/ps'); + /** * example * @class @@ -7,11 +15,325 @@ class OsController { /** - * test + * 所有方法接收两个参数 + * @param args 前端传的参数 + * @param event - ipc通信时才有值。详情见:控制器文档 */ - async test () { - return 'hello electron-egg'; + + /** + * 消息提示对话框 + */ + messageShow() { + dialog.showMessageBoxSync({ + type: 'info', // "none", "info", "error", "question" 或者 "warning" + title: '自定义标题-message', + message: '自定义消息内容', + detail: '其它的额外信息' + }) + + return '打开了消息框'; } + + /** + * 消息提示与确认对话框 + */ + messageShowConfirm() { + const res = dialog.showMessageBoxSync({ + type: 'info', + title: '自定义标题-message', + message: '自定义消息内容', + detail: '其它的额外信息', + cancelId: 1, // 用于取消对话框的按钮的索引 + defaultId: 0, // 设置默认选中的按钮 + buttons: ['确认', '取消'], // 按钮及索引 + }) + let data = (res === 0) ? '点击确认按钮' : '点击取消按钮'; + + return data; + } + + /** + * 选择目录 + */ + selectFolder() { + const filePaths = dialog.showOpenDialogSync({ + properties: ['openDirectory', 'createDirectory'] + }); + + if (_.isEmpty(filePaths)) { + return null + } + + return filePaths[0]; + } + + /** + * 打开目录 + */ + openDirectory(args) { + const { id } = args; + if (!id) { + return false; + } + let dir = ''; + if (path.isAbsolute(id)) { + dir = id; + } else { + dir = electronApp.getPath(id); + } + + shell.openPath(dir); + return true; + } + + /** + * 选择图片 + */ + selectPic() { + const filePaths = dialog.showOpenDialogSync({ + title: 'select pic', + properties: ['openFile'], + filters: [ + { name: 'Images', extensions: ['jpg', 'png', 'gif'] }, + ] + }); + if (_.isEmpty(filePaths)) { + return null + } + + return filePaths[0]; + } + + /** + * 加载视图内容 + */ + loadViewContent(args) { + const { type, content } = args; + let contentUrl = content; + if (type == 'html') { + contentUrl = path.join('file://', electronApp.getAppPath(), content); + } + + Services.get('os').createBrowserView(contentUrl); + + return true + } + + /** + * 移除视图内容 + */ + removeViewContent() { + Services.get('os').removeBrowserView(); + return true + } + + /** + * 打开新窗口 + */ + createWindow(args) { + const { type, content, windowName, windowTitle } = args; + let contentUrl = null; + if (type == 'html') { + contentUrl = path.join('file://', electronApp.getAppPath(), content) + } else if (type == 'web') { + contentUrl = content; + } else if (type == 'vue') { + let addr = 'http://localhost:8080' + if (isProd()) { + const mainServer = Conf.getValue('mainServer'); + if (Conf.isFileProtocol(mainServer)) { + addr = mainServer.protocol + path.join(Ps.getHomeDir(), mainServer.indexPath); + } else { + addr = mainServer.protocol + mainServer.host + ':' + mainServer.port; + } + } + + contentUrl = addr + content; + } else { + // some + } + + console.log('contentUrl: ', contentUrl); + let opt = { + title: windowTitle + } + const win = Addon.get('window').create(windowName, opt); + const winContentsId = win.webContents.id; + + // load page + win.loadURL(contentUrl); + + return winContentsId; + } + + /** + * 获取窗口contents id + */ + getWCid(args) { + // 主窗口的name默认是main,其它窗口name开发者自己定义 + const name = args; + const id = Addon.get('window').getWCid(name); + + return id; + } + + /** + * 创建系统通知 + */ + sendNotification(args, event) { + const { title, subtitle, body, silent} = args; + + if (!Notification.isSupported()) { + return '当前系统不支持通知'; + } + + let options = {}; + if (!_.isEmpty(title)) { + options.title = title; + } + if (!_.isEmpty(subtitle)) { + options.subtitle = subtitle; + } + if (!_.isEmpty(body)) { + options.body = body; + } + if (!_.isEmpty(silent)) { + options.silent = silent; + } + + Services.get('os').createNotification(options, event); + + return true + } + + /** + * 电源监控 + */ + initPowerMonitor(args, event) { + const channel = 'controller.os.initPowerMonitor'; + powerMonitor.on('on-ac', (e) => { + let data = { + type: 'on-ac', + msg: '接入了电源' + } + event.reply(`${channel}`, data) + }); + + powerMonitor.on('on-battery', (e) => { + let data = { + type: 'on-battery', + msg: '使用电池中' + } + event.reply(`${channel}`, data) + }); + + powerMonitor.on('lock-screen', (e) => { + let data = { + type: 'lock-screen', + msg: '锁屏了' + } + event.reply(`${channel}`, data) + }); + + powerMonitor.on('unlock-screen', (e) => { + let data = { + type: 'unlock-screen', + msg: '解锁了' + } + event.reply(`${channel}`, data) + }); + + return true + } + + /** + * 获取屏幕信息 + */ + getScreen(args) { + let data = []; + let res = {}; + if (args == 0) { + let res = screen.getCursorScreenPoint(); + data = [ + { + title: '横坐标', + desc: res.x + }, + { + title: '纵坐标', + desc: res.y + }, + ] + + return data; + } + if (args == 1) { + res = screen.getPrimaryDisplay(); + } + if (args == 2) { + let resArr = screen.getAllDisplays(); + // 数组,只取一个吧 + res = resArr[0]; + } + // Log.info('[electron] [ipc] [example] [getScreen] res:', res); + data = [ + { + title: '分辨率', + desc: res.bounds.width + ' x ' + res.bounds.height + }, + { + title: '单色显示器', + desc: res.monochrome ? '是' : '否' + }, + { + title: '色深', + desc: res. colorDepth + }, + { + title: '色域', + desc: res.colorSpace + }, + { + title: 'scaleFactor', + desc: res.scaleFactor + }, + { + title: '加速器', + desc: res.accelerometerSupport + }, + { + title: '触控', + desc: res.touchSupport == 'unknown' ? '不支持' : '支持' + }, + ] + + return data; + } + + /** + * 获取系统主题 + */ + getTheme() { + let theme = 'system'; + if (nativeTheme.shouldUseHighContrastColors) { + theme = 'light'; + } else if (nativeTheme.shouldUseInvertedColorScheme) { + theme = 'dark'; + } + + return theme; + } + + /** + * 设置系统主题 + */ + setTheme(args) { + + // TODO 好像没有什么明显效果 + nativeTheme.themeSource = args; + + return args; + } } OsController.toString = () => '[class OsController]'; diff --git a/electron/service/cross.js b/electron/service/cross.js new file mode 100644 index 0000000..64ff240 --- /dev/null +++ b/electron/service/cross.js @@ -0,0 +1,101 @@ +'use strict'; + +const { logger } = require('ee-core/log'); +const { getExtraResourcesDir } = require('ee-core/ps'); +const path = require("path"); +const Is = require('ee-core/utils/is'); + +/** + * cross + * @class + */ +class CrossService { + + /** + * create go service + * In the default configuration, services can be started with applications. + * Developers can turn off the configuration and create it manually. + */ + async createGoServer() { + // method 1: Use the default Settings + //const entity = await Cross.run(serviceName); + + // method 2: Use custom configuration + const serviceName = "go"; + const opt = { + name: 'goapp', + cmd: path.join(getExtraResourcesDir(), 'goapp'), + directory: getExtraResourcesDir(), + args: ['--port=7073'], + appExit: true, + } + const entity = await Cross.run(serviceName, opt); + logger.info('server name:', entity.name); + logger.info('server config:', entity.config); + logger.info('server url:', entity.getUrl()); + + return; + } + + /** + * create java server + */ + async createJavaServer() { + const serviceName = "java"; + const jarPath = path.join(getExtraResourcesDir(), 'java-app.jar'); + const opt = { + name: 'javaapp', + cmd: path.join(getExtraResourcesDir(), 'jre1.8.0_201/bin/javaw.exe'), + directory: getExtraResourcesDir(), + args: ['-jar', '-server', '-Xms512M', '-Xmx512M', '-Xss512k', '-Dspring.profiles.active=prod', `-Dserver.port=18080`, `-Dlogging.file.path=${Ps.getLogDir()}`, `${jarPath}`], + appExit: false, + } + if (Is.macOS()) { + // Setup Java program + opt.cmd = path.join(Ps.getExtraResourcesDir(), 'jre1.8.0_201.jre/Contents/Home/bin/java'); + } + if (Is.linux()) { + // Setup Java program + } + + const entity = await Cross.run(serviceName, opt); + logger.info('server name:', entity.name); + logger.info('server config:', entity.config); + logger.info('server url:', Cross.getUrl(entity.name)); + + return; + } + + /** + * create python service + * In the default configuration, services can be started with applications. + * Developers can turn off the configuration and create it manually. + */ + async createPythonServer() { + // method 1: Use the default Settings + //const entity = await Cross.run(serviceName); + + // method 2: Use custom configuration + const serviceName = "python"; + const opt = { + name: 'pyapp', + cmd: path.join(getExtraResourcesDir(), 'py', 'pyapp'), + directory: path.join(getExtraResourcesDir(), 'py'), + args: ['--port=7074'], + windowsExtname: true, + appExit: true, + } + const entity = await Cross.run(serviceName, opt); + logger.info('server name:', entity.name); + logger.info('server config:', entity.config); + logger.info('server url:', entity.getUrl()); + + return; + } +} + +CrossService.toString = () => '[class CrossService]'; +module.exports = { + CrossService, + crossService: new CrossService() +}; \ No newline at end of file diff --git a/electron/service/database/sqlitedb.js b/electron/service/database/sqlitedb.js new file mode 100644 index 0000000..7971e03 --- /dev/null +++ b/electron/service/database/sqlitedb.js @@ -0,0 +1,163 @@ +'use strict'; + +const Storage = require('ee-core/storage'); +const _ = require('lodash'); +const path = require('path'); + +/** + * sqlite数据存储 + * @class + */ +class SqlitedbService { + + constructor () { + this.sqliteFile = 'sqlite-demo.db'; + let sqliteOptions = { + driver: 'sqlite', + default: { + timeout: 6000, + verbose: console.log // 打印sql语法 + } + } + this.demoSqliteDB = Storage.connection(this.sqliteFile, sqliteOptions); + } + + /* + * 检查并创建表 (sqlite) + */ + async checkAndCreateTableSqlite(tableName = '') { + if (_.isEmpty(tableName)) { + throw new Error(`table name is required`); + } + // 检查表是否存在 + const userTable = this.demoSqliteDB.db.prepare('SELECT * FROM sqlite_master WHERE type=? AND name = ?'); + const result = userTable.get('table', tableName); + //console.log('result:', result); + if (result) { + return; + } + + // 创建表 + const create_table_user = + `CREATE TABLE ${tableName} + ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name CHAR(50) NOT NULL, + age INT + );` + this.demoSqliteDB.db.exec(create_table_user); + + } + + /* + * 增 Test data (sqlite) + */ + async addTestDataSqlite(data) { + //console.log("add data:", data); + + let table = 'user'; + await this.checkAndCreateTableSqlite(table); + + const insert = this.demoSqliteDB.db.prepare(`INSERT INTO ${table} (name, age) VALUES (@name, @age)`); + insert.run(data); + + return true; + } + + /* + * 删 Test data (sqlite) + */ + async delTestDataSqlite(name = '') { + //console.log("delete name:", name); + + let table = 'user'; + await this.checkAndCreateTableSqlite(table); + + const delUser = this.demoSqliteDB.db.prepare(`DELETE FROM ${table} WHERE name = ?`); + delUser.run(name); + + return true; + } + + /* + * 改 Test data (sqlite) + */ + async updateTestDataSqlite(name= '', age = 0) { + //console.log("update :", {name, age}); + + let table = 'user'; + await this.checkAndCreateTableSqlite(table); + + const updateUser = this.demoSqliteDB.db.prepare(`UPDATE ${table} SET age = ? WHERE name = ?`); + updateUser.run(age, name); + + return true; + } + + /* + * 查 Test data (sqlite) + */ + async getTestDataSqlite(age = 0) { + //console.log("select :", {age}); + + let table = 'user'; + await this.checkAndCreateTableSqlite(table); + + const selectUser = this.demoSqliteDB.db.prepare(`SELECT * FROM ${table} WHERE age = @age`); + const users = selectUser.all({age: age}); + //console.log("select users:", users); + return users; + } + + /* + * all Test data (sqlite) + */ + async getAllTestDataSqlite() { + //console.log("select all user"); + + let table = 'user'; + await this.checkAndCreateTableSqlite(table); + + const selectAllUser = this.demoSqliteDB.db.prepare(`SELECT * FROM ${table} `); + const allUser = selectAllUser.all(); + //console.log("select allUser:", allUser); + return allUser; + } + + /* + * get data dir (sqlite) + */ + async getDataDir() { + const dir = this.demoSqliteDB.getStorageDir(); + + return dir; + } + + /* + * set custom data dir (sqlite) + */ + async setCustomDataDir(dir) { + if (_.isEmpty(dir)) { + return; + } + + // the absolute path of the db file + const dbFile = path.join(dir, this.sqliteFile); + const sqliteOptions = { + driver: 'sqlite', + default: { + timeout: 6000, + verbose: console.log + } + } + this.demoSqliteDB = Storage.connection(dbFile, sqliteOptions); + + return; + } +} + +SqlitedbService.toString = () => '[class SqlitedbService]'; +module.exports = { + SqlitedbService, + sqlitedbService: new SqlitedbService() +}; diff --git a/electron/service/effect.js b/electron/service/effect.js new file mode 100644 index 0000000..06bee6e --- /dev/null +++ b/electron/service/effect.js @@ -0,0 +1,30 @@ +'use strict'; + +const { logger } = require('ee-core/log'); + +/** + * effect + * @class + */ +class EffectService { + + /** + * hello + */ + async hello(args) { + let obj = { + status:'ok', + params: args + } + logger.info('EffectService obj:', obj); + + return obj; + } + +} + +EffectService.toString = () => '[class EffectService]'; +module.exports = { + EffectService, + effectService: new EffectService() +}; \ No newline at end of file diff --git a/electron/service/example.js b/electron/service/example.js index 99a81c3..d7fab41 100644 --- a/electron/service/example.js +++ b/electron/service/example.js @@ -1,16 +1,12 @@ 'use strict'; -const { Service } = require('ee-core'); +const { logger } = require('ee-core/log'); /** - * 示例服务(service层为单例) + * 示例服务 * @class */ -class ExampleService extends Service { - - constructor(ctx) { - super(ctx); - } +class ExampleService { /** * test @@ -21,9 +17,16 @@ class ExampleService extends Service { params: args } + logger.info('ExampleService obj:', obj); + + //Services.get('framework').test('egg'); + return obj; } } ExampleService.toString = () => '[class ExampleService]'; -module.exports = ExampleService; \ No newline at end of file +module.exports = { + ExampleService, + exampleService: new ExampleService() +}; \ No newline at end of file diff --git a/electron/service/framework.js b/electron/service/framework.js new file mode 100644 index 0000000..67411ee --- /dev/null +++ b/electron/service/framework.js @@ -0,0 +1,164 @@ +'use strict'; + +const { logger } = require('ee-core/log'); +const { ChildJob, ChildPoolJob } = require('ee-core/jobs'); +const Ps = require('ee-core/ps'); + +/** + * framework + * @class + */ +class FrameworkService { + + constructor() { + // 在构造函数中初始化一些变量 + this.myTimer = null; + this.myJob = new ChildJob(); + this.myJobPool = new ChildPoolJob(); + this.taskForJob = {}; + } + + /** + * test + */ + async test(args) { + let obj = { + status:'ok', + params: args + } + logger.info('FrameworkService obj:', obj); + return obj; + } + + /** + * ipc通信(双向) + */ + bothWayMessage(type, content, event) { + // 前端ipc频道 channel + const channel = 'controller.framework.ipcSendMsg'; + + if (type == 'start') { + // 每隔1秒,向前端页面发送消息 + // 用定时器模拟 + this.myTimer = setInterval(function(e, c, msg) { + let timeNow = Date.now(); + let data = msg + ':' + timeNow; + e.reply(`${c}`, data) + }, 1000, event, channel, content) + + return '开始了' + } else if (type == 'end') { + clearInterval(this.myTimer); + return '停止了' + } else { + return 'ohther' + } + } + + /** + * 执行任务 + */ + doJob(jobId, action, event) { + let res = {}; + let oneTask; + const channel = 'controller.framework.timerJobProgress'; + if (action == 'create') { + // 执行任务及监听进度 + let eventName = 'job-timer-progress-' + jobId; + const timerTask = this.myJob.exec('./jobs/example/timer', {jobId}); + timerTask.emitter.on(eventName, (data) => { + logger.info('[main-process] timerTask, from TimerJob data:', data); + // 发送数据到渲染进程 + event.sender.send(`${channel}`, data) + }) + + // 执行任务及监听进度 异步 + // myjob.execPromise('./jobs/example/timer', {jobId}).then(task => { + // task.emitter.on(eventName, (data) => { + // Log.info('[main-process] timerTask, from TimerJob data:', data); + // // 发送数据到渲染进程 + // event.sender.send(`${channel}`, data) + // }) + // }); + + res.pid = timerTask.pid; + this.taskForJob[jobId] = timerTask; + } + if (action == 'close') { + oneTask = this.taskForJob[jobId]; + oneTask.kill(); + event.sender.send(`${channel}`, {jobId, number:0, pid:0}); + } + if (action == 'pause') { + oneTask = this.taskForJob[jobId]; + oneTask.callFunc('./jobs/example/timer', 'pause', jobId); + } + if (action == 'resume') { + oneTask = this.taskForJob[jobId]; + oneTask.callFunc('./jobs/example/timer', 'resume', jobId, oneTask.pid); + } + + return res; + } + + + + /** + * 创建pool + */ + doCreatePool(num, event) { + const channel = 'controller.framework.createPoolNotice'; + this.myJobPool.create(num).then(pids => { + event.reply(`${channel}`, pids); + }); + } + + /** + * 通过进程池执行任务 + */ + doJobByPool(jobId, action, event) { + let res = {}; + const channel = 'controller.framework.timerJobProgress'; + if (action == 'run') { + // 异步-执行任务及监听进度 + this.myJobPool.runPromise('./jobs/example/timer', {jobId}).then(task => { + + // 监听器名称唯一,否则会出现重复监听。 + // 任务完成时,需要移除监听器,防止内存泄漏 + let eventName = 'job-timer-progress-' + jobId; + task.emitter.on(eventName, (data) => { + logger.info('[main-process] [ChildPoolJob] timerTask, from TimerJob data:', data); + + // 发送数据到渲染进程 + event.sender.send(`${channel}`, data) + + // 如果收到任务完成的消息,移除监听器 + if (data.end) { + task.emitter.removeAllListeners(eventName); + } + }); + + res.pid = task.pid; + }); + } + return res; + } + + /** + * 获取正在运行的 job 进程 + */ + monitorJob() { + setInterval(() => { + let jobPids = this.myJob.getPids(); + let jobPoolPids = this.myJobPool.getPids(); + logger.info(`[main-process] [monitorJob] jobPids: ${jobPids}, jobPoolPids: ${jobPoolPids}`); + }, 5000) + } + +} + +FrameworkService.toString = () => '[class FrameworkService]'; +module.exports = { + FrameworkService, + frameworkService: new FrameworkService() +}; \ No newline at end of file diff --git a/electron/service/job/user.js b/electron/service/job/user.js new file mode 100644 index 0000000..05f87a5 --- /dev/null +++ b/electron/service/job/user.js @@ -0,0 +1,28 @@ +'use strict'; + +const { logger } = require('ee-core/log'); + +/** + * job 中使用的 service 不要继承 const { Service } = require('ee-core') + * 因为 Service 中会依赖 electron 的 api 导致错误 + * @class + */ +class UserService { + + /** + * hello + */ + async hello(args) { + let obj = { + status:'ok', + params: args + } + logger.info('UserService obj:', obj); + + return obj; + } + +} + +UserService.toString = () => '[class UserService]'; +module.exports = UserService; \ No newline at end of file diff --git a/electron/service/os.js b/electron/service/os.js new file mode 100644 index 0000000..f848d17 --- /dev/null +++ b/electron/service/os.js @@ -0,0 +1,88 @@ +'use strict'; + +const { BrowserView, Notification } = require('electron'); +const { getMainWindow } = require('ee-core/electron/window'); + +/** + * os + * @class + */ +class OsService { + + constructor() { + this.myBrowserView = null; + this.myNotification = null; + } + + /** + * createBrowserView + */ + createBrowserView(contentUrl) { + + // electron 实验性功能,慎用 + const win = getMainWindow(); + this.myBrowserView = new BrowserView(); + win.setBrowserView(this.myBrowserView); + this.myBrowserView.setBounds({ + x: 300, + y: 170, + width: 650, + height: 400 + }); + this.myBrowserView.webContents.loadURL(contentUrl); + } + + /** + * removeBrowserView + */ + removeBrowserView() { + // one + this.myBrowserView.webContents.loadURL('about:blank') + + // two - electron 11 remove destroy() + // this.myBrowserView.webContents.destroy(); + + // three + // this.myBrowserView.webContents.forcefullyCrashRenderer() + + // fore + // this.myBrowserView.webContents.close + } + + /** + * createNotification + */ + createNotification(options, event) { + const channel = 'controller.os.sendNotification'; + this.myNotification = new Notification(options); + + if (options.clickEvent) { + this.myNotification.on('click', (e) => { + let data = { + type: 'click', + msg: '您点击了通知消息' + } + event.reply(`${channel}`, data) + }); + } + + if (options.closeEvent) { + this.myNotification.on('close', (e) => { + let data = { + type: 'close', + msg: '您关闭了通知消息' + } + event.reply(`${channel}`, data) + }); + } + + this.myNotification.show(); + } + +} + +OsService.toString = () => '[class OsService]'; +module.exports = { + OsService, + osService: new OsService() +}; \ No newline at end of file