diff --git a/build/extraResources/chromeExtension/read.txt b/build/extraResources/chromeExtension/read.txt new file mode 100644 index 0000000..57b4b84 --- /dev/null +++ b/build/extraResources/chromeExtension/read.txt @@ -0,0 +1 @@ +chrome应用商店ctx文件,解压后,放置在此目录中,打包时会将资源加入安装包内。 \ No newline at end of file diff --git a/build/extraResources/dll/myDllDemo.dll b/build/extraResources/dll/myDllDemo.dll new file mode 100644 index 0000000..ffd5446 Binary files /dev/null and b/build/extraResources/dll/myDllDemo.dll differ diff --git a/build/extraResources/goapp b/build/extraResources/goapp new file mode 100755 index 0000000..fbb62d0 Binary files /dev/null and b/build/extraResources/goapp differ diff --git a/build/extraResources/read.txt b/build/extraResources/read.txt new file mode 100644 index 0000000..e20ade3 --- /dev/null +++ b/build/extraResources/read.txt @@ -0,0 +1 @@ +建议第三方软件放置在此目录中,打包时会将资源加入安装包内。 \ No newline at end of file diff --git a/build/icons/128x128.png b/build/icons/128x128.png new file mode 100644 index 0000000..56b37e1 Binary files /dev/null and b/build/icons/128x128.png differ diff --git a/build/icons/16x16.png b/build/icons/16x16.png new file mode 100644 index 0000000..fc40055 Binary files /dev/null and b/build/icons/16x16.png differ diff --git a/build/icons/256x256.png b/build/icons/256x256.png new file mode 100644 index 0000000..95dc60b Binary files /dev/null and b/build/icons/256x256.png differ diff --git a/build/icons/32x32.png b/build/icons/32x32.png new file mode 100644 index 0000000..ea7b2df Binary files /dev/null and b/build/icons/32x32.png differ diff --git a/build/icons/48x48.png b/build/icons/48x48.png new file mode 100644 index 0000000..a85fc93 Binary files /dev/null and b/build/icons/48x48.png differ diff --git a/build/icons/512x512.png b/build/icons/512x512.png new file mode 100644 index 0000000..df48e85 Binary files /dev/null and b/build/icons/512x512.png differ diff --git a/build/icons/64x64.png b/build/icons/64x64.png new file mode 100644 index 0000000..87b8273 Binary files /dev/null and b/build/icons/64x64.png differ diff --git a/build/icons/icon.icns b/build/icons/icon.icns new file mode 100644 index 0000000..aa79460 Binary files /dev/null and b/build/icons/icon.icns differ diff --git a/build/icons/icon.ico b/build/icons/icon.ico new file mode 100644 index 0000000..aefab6a Binary files /dev/null and b/build/icons/icon.ico differ diff --git a/build/icons/icon.png b/build/icons/icon.png new file mode 100644 index 0000000..df48e85 Binary files /dev/null and b/build/icons/icon.png differ diff --git a/build/script/installer.nsh b/build/script/installer.nsh new file mode 100644 index 0000000..e69de29 diff --git a/cmd/bin.js b/cmd/bin.js new file mode 100644 index 0000000..c00e5a3 --- /dev/null +++ b/cmd/bin.js @@ -0,0 +1,195 @@ +/** + * ee-bin 配置 + * 仅适用于开发环境 + */ +module.exports = { + /** + * development serve ("frontend" "electron" ) + * ee-bin dev + */ + dev: { + frontend: { + directory: './frontend', + cmd: 'npm', + args: ['run', 'dev'], + port: 8080, + }, + electron: { + directory: './', + cmd: 'electron', + args: ['.', '--env=local'], + watch: true, + } + }, + + /** + * 构建 + * ee-bin build + */ + build: { + frontend: { + directory: './frontend', + cmd: 'npm', + args: ['run', 'build'], + }, + electron: { + type: 'javascript', + bundleType: 'copy' + }, + win64: { + cmd: 'electron-builder', + directory: './', + args: ['--config=./cmd/builder.json', '-w=nsis', '--x64'], + }, + win32: { + args: ['--config=./cmd/builder.json', '-w=nsis', '--ia32'], + }, + win_e: { + args: ['--config=./cmd/builder.json', '-w=portable', '--x64'], + }, + win_7z: { + args: ['--config=./cmd/builder.json', '-w=7z', '--x64'], + }, + mac: { + args: ['--config=./cmd/builder-mac.json', '-m'], + }, + mac_arm64: { + args: ['--config=./cmd/builder-mac-arm64.json', '-m', '--arm64'], + }, + linux: { + args: ['--config=./cmd/builder-linux.json', '-l=deb', '--x64'], + }, + linux_arm64: { + args: ['--config=./cmd/builder-linux.json', '-l=deb', '--arm64'], + }, + go_w: { + directory: './go', + cmd: 'go', + args: ['build', '-o=../build/extraResources/goapp.exe'], + }, + go_m: { + directory: './go', + cmd: 'go', + args: ['build', '-o=../build/extraResources/goapp'], + }, + go_l: { + directory: './go', + cmd: 'go', + args: ['build', '-o=../build/extraResources/goapp'], + }, + python: { + directory: './python', + cmd: 'python', + args: ['./setup.py', 'build'], + }, + }, + + /** + * 移动资源 + * ee-bin move + */ + move: { + frontend_dist: { + src: './frontend/dist', + dest: './public/dist' + }, + go_static: { + src: './frontend/dist', + dest: './go/public/dist' + }, + go_config: { + src: './go/config', + dest: './go/public/config' + }, + go_package: { + src: './package.json', + dest: './go/public/package.json' + }, + go_images: { + src: './public/images', + dest: './go/public/images' + }, + python_dist: { + src: './python/dist', + dest: './build/extraResources/py' + }, + }, + + /** + * 预发布模式(prod) + * ee-bin start + */ + start: { + directory: './', + cmd: 'electron', + args: ['.', '--env=prod'] + }, + + /** + * 加密 + */ + encrypt: { + frontend: { + type: 'none', + files: [ + './public/dist/**/*.(js|json)', + ], + cleanFiles: ['./public/dist'], + confusionOptions: { + compact: true, + stringArray: true, + stringArrayEncoding: ['none'], + stringArrayCallsTransform: true, + numbersToExpressions: true, + target: 'browser', + } + }, + electron: { + type: 'confusion', + files: [ + './public/electron/**/*.(js|json)', + ], + cleanFiles: ['./public/electron'], + confusionOptions: { + compact: true, + stringArray: true, + stringArrayEncoding: ['rc4'], + deadCodeInjection: false, + stringArrayCallsTransform: true, + numbersToExpressions: true, + target: 'node', + } + } + }, + + /** + * 执行自定义命令 + * ee-bin exec + */ + exec: { + // 单独调试,air 实现 go 热重载 + go: { + directory: './go', + cmd: 'air', + args: ['-c=config/.air.toml' ], + }, + // windows 单独调试,air 实现 go 热重载 + go_w: { + directory: './go', + cmd: 'air', + args: ['-c=config/.air.windows.toml' ], + }, + // 单独调试,以基础方式启动 go + go2: { + directory: './go', + cmd: 'go', + args: ['run', './main.go', '--env=dev','--basedir=../', '--port=7073'], + }, + python: { + directory: './python', + cmd: 'python', + args: ['./main.py', '--port=7074'], + stdio: "inherit", // ignore + }, + }, +}; \ No newline at end of file diff --git a/cmd/builder-linux.json b/cmd/builder-linux.json new file mode 100644 index 0000000..eb4dfb2 --- /dev/null +++ b/cmd/builder-linux.json @@ -0,0 +1,40 @@ +{ + "productName": "ee", + "appId": "com.bilibili.ee", + "copyright": "© 2025 duola Technology Co., Ltd.", + "directories": { + "output": "out" + }, + "asar": true, + "files": [ + "**/*", + "!cmd/", + "!data/", + "!electron/", + "!frontend/", + "!logs/", + "!out/", + "!go/", + "!python/" + ], + "extraResources": [ + { + "from": "build/extraResources", + "to": "extraResources" + } + ], + "publish": [ + { + "provider": "generic", + "url": "" + } + ], + "linux": { + "icon": "build/icons/icon.icns", + "artifactName": "${productName}-${os}-${version}-${arch}.${ext}", + "target": [ + "deb" + ], + "category": "Utility" + } +} \ No newline at end of file diff --git a/cmd/builder-mac-arm64.json b/cmd/builder-mac-arm64.json new file mode 100644 index 0000000..b110fc6 --- /dev/null +++ b/cmd/builder-mac-arm64.json @@ -0,0 +1,38 @@ +{ + "productName": "ee", + "appId": "com.bilibili.ee", + "copyright": "© 2025 duola Technology Co., Ltd.", + "directories": { + "output": "out" + }, + "asar": true, + "files": [ + "**/*", + "!cmd/", + "!data/", + "!electron/", + "!frontend/", + "!logs/", + "!out/", + "!go/", + "!python/" + ], + "extraResources": [ + { + "from": "build/extraResources", + "to": "extraResources" + } + ], + "publish": [ + { + "provider": "generic", + "url": "" + } + ], + "mac": { + "icon": "build/icons/icon.icns", + "artifactName": "${productName}-${os}-${version}-${arch}.${ext}", + "darkModeSupport": true, + "hardenedRuntime": false + } +} \ No newline at end of file diff --git a/cmd/builder-mac.json b/cmd/builder-mac.json new file mode 100644 index 0000000..b110fc6 --- /dev/null +++ b/cmd/builder-mac.json @@ -0,0 +1,38 @@ +{ + "productName": "ee", + "appId": "com.bilibili.ee", + "copyright": "© 2025 duola Technology Co., Ltd.", + "directories": { + "output": "out" + }, + "asar": true, + "files": [ + "**/*", + "!cmd/", + "!data/", + "!electron/", + "!frontend/", + "!logs/", + "!out/", + "!go/", + "!python/" + ], + "extraResources": [ + { + "from": "build/extraResources", + "to": "extraResources" + } + ], + "publish": [ + { + "provider": "generic", + "url": "" + } + ], + "mac": { + "icon": "build/icons/icon.icns", + "artifactName": "${productName}-${os}-${version}-${arch}.${ext}", + "darkModeSupport": true, + "hardenedRuntime": false + } +} \ No newline at end of file diff --git a/cmd/builder.json b/cmd/builder.json new file mode 100644 index 0000000..c1c2a05 --- /dev/null +++ b/cmd/builder.json @@ -0,0 +1,50 @@ +{ + "productName": "ee", + "appId": "com.electron.ee", + "copyright": "© 2025 duola Technology Co., Ltd.", + "directories": { + "output": "out" + }, + "asar": true, + "files": [ + "**/*", + "!cmd/", + "!data/", + "!electron/", + "!frontend/", + "!logs/", + "!out/", + "!go/", + "!python/" + ], + "extraResources": { + "from": "build/extraResources/", + "to": "extraResources" + }, + "nsis": { + "oneClick": false, + "allowElevation": true, + "allowToChangeInstallationDirectory": true, + "installerIcon": "build/icons/icon.ico", + "uninstallerIcon": "build/icons/icon.ico", + "installerHeaderIcon": "build/icons/icon.ico", + "createDesktopShortcut": true, + "createStartMenuShortcut": true, + "shortcutName": "ee" + }, + "publish": [ + { + "provider": "generic", + "url": "https://github.com/wallace5303/electron-egg" + } + ], + "win": { + "icon": "build/icons/icon.ico", + "artifactName": "${productName}-${os}-${version}-${arch}.${ext}", + "target": [ + { + "target": "nsis" + } + ] + } +} \ No newline at end of file diff --git a/electron/config/config.default.js b/electron/config/config.default.js new file mode 100644 index 0000000..70b4719 --- /dev/null +++ b/electron/config/config.default.js @@ -0,0 +1,69 @@ +'use strict'; + +const path = require('path'); +const { getBaseDir } = require('ee-core/ps'); + +/** + * 默认配置 + */ +module.exports = () => { + return { + openDevTools: false, + singleLock: true, + windowsOption: { + title: 'electron-egg', + width: 980, + height: 650, + minWidth: 400, + minHeight: 300, + webPreferences: { + //webSecurity: false, + contextIsolation: false, // false -> 可在渲染进程中使用electron的api,true->需要bridge.js(contextBridge) + nodeIntegration: true, + //preload: path.join(getElectronDir(), 'preload', 'bridge.js'), + }, + frame: true, + show: true, + icon: path.join(getBaseDir(), 'public', 'images', 'logo-32.png'), + }, + logger: { + level: 'INFO', + outputJSON: false, + appLogName: 'ee.log', + coreLogName: 'ee-core.log', + errorLogName: 'ee-error.log' + }, + remote: { + enable: false, + url: 'http://electron-egg.kaka996.com/' + }, + socketServer: { + enable: false, + port: 7070, + path: "/socket.io/", + connectTimeout: 45000, + pingTimeout: 30000, + pingInterval: 25000, + maxHttpBufferSize: 1e8, + transports: ["polling", "websocket"], + cors: { + origin: true, + }, + channel: 'socket-channel' + }, + httpServer: { + enable: false, + https: { + enable: false, + key: '/public/ssl/localhost+1.key', + cert: '/public/ssl/localhost+1.pem' + }, + host: '127.0.0.1', + port: 7071, + }, + mainServer: { + indexPath: '/public/dist/index.html', + channelSeparator: '/', + } + } +} diff --git a/electron/config/config.local.js b/electron/config/config.local.js new file mode 100644 index 0000000..0822a50 --- /dev/null +++ b/electron/config/config.local.js @@ -0,0 +1,15 @@ +'use strict'; + +/** + * Development environment configuration, coverage config.default.js + */ +module.exports = () => { + return { + openDevTools: { + mode: 'bottom' + }, + jobs: { + messageLog: false + } + }; +}; diff --git a/electron/config/config.prod.js b/electron/config/config.prod.js new file mode 100644 index 0000000..db1ce01 --- /dev/null +++ b/electron/config/config.prod.js @@ -0,0 +1,10 @@ +'use strict'; + +/** + * coverage config.default.js + */ +module.exports = () => { + return { + openDevTools: false, + }; +}; diff --git a/electron/controller/cross.js b/electron/controller/cross.js new file mode 100644 index 0000000..4bd7465 --- /dev/null +++ b/electron/controller/cross.js @@ -0,0 +1,65 @@ +'use strict'; + +const { crossService } = require('../service/cross'); + +/** + * Cross + * @class + */ +class CrossController { + + /** + * View process service information + */ + info() { + crossService.info(); + return 'hello electron-egg'; + } + + /** + * Get service url + */ + async getUrl(args) { + const { name } = args; + const serverUrl = crossService.getUrl(name); + return serverUrl; + } + + /** + * kill service + * By default (modifiable), killing the process will exit the electron application. + */ + async killServer(args) { + const { type, name } = args; + crossService.killServer(type, name); + return; + } + + /** + * create service + */ + async createServer(args) { + const { program } = args; + if (program == 'go') { + crossService.createGoServer(); + } else if (program == 'java') { + crossService.createJavaServer(); + } else if (program == 'python') { + crossService.createPythonServer(); + } + + return; + } + + /** + * Access the api for the cross service + */ + async requestApi(args) { + const { name, urlPath, params} = args; + const data = await crossService.requestApi(name, urlPath, params); + return data; + } +} +CrossController.toString = () => '[class CrossController]'; + +module.exports = CrossController; \ No newline at end of file diff --git a/electron/controller/effect.js b/electron/controller/effect.js new file mode 100644 index 0000000..026812a --- /dev/null +++ b/electron/controller/effect.js @@ -0,0 +1,65 @@ +'use strict'; + +const { dialog } = require('electron'); +const { getMainWindow } = require('ee-core/electron'); + +/** + * effect - demo + * @class + */ +class EffectController { + + /** + * select file + */ + selectFile() { + const filePaths = dialog.showOpenDialogSync({ + properties: ['openFile'] + }); + + if (!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]'; + +module.exports = EffectController; \ No newline at end of file diff --git a/electron/controller/example.js b/electron/controller/example.js new file mode 100644 index 0000000..61e1afd --- /dev/null +++ b/electron/controller/example.js @@ -0,0 +1,18 @@ +'use strict'; + +/** + * example + * @class + */ +class ExampleController { + + /** + * test + */ + async test () { + return 'hello electron-egg'; + } +} +ExampleController.toString = () => '[class ExampleController]'; + +module.exports = ExampleController; \ No newline at end of file diff --git a/electron/controller/framework.js b/electron/controller/framework.js new file mode 100644 index 0000000..2af8f8e --- /dev/null +++ b/electron/controller/framework.js @@ -0,0 +1,270 @@ +'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'); +const { frameworkService } = require('../service/framework'); +const { sqlitedbService } = require('../service/database/sqlitedb'); +const { autoUpdaterService } = require('../service/os/auto_updater'); + +/** + * framework - demo + * @class + */ +class FrameworkController { + + /** + * 所有方法接收两个参数 + * @param args 前端传的参数 + * @param event - ipc通信时才有值。详情见:控制器文档 + */ + + /** + * 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 + sqlitedbService.getDataDir(); + } catch (err) { + console.log(err); + data.code = -1; + return data; + } + + switch (action) { + case 'add' : + data.result = await sqlitedbService.addTestDataSqlite(info);; + break; + case 'del' : + data.result = await sqlitedbService.delTestDataSqlite(delete_name);; + break; + case 'update' : + data.result = await sqlitedbService.updateTestDataSqlite(update_name, update_age); + break; + case 'get' : + data.result = await sqlitedbService.getTestDataSqlite(search_age); + break; + case 'getDataDir' : + data.result = await sqlitedbService.getDataDir(); + break; + case 'setDataDir' : + data.result = await sqlitedbService.setCustomDataDir(data_dir); + break; + } + + data.all_list = await sqlitedbService.getAllTestDataSqlite(); + + return data; + } + + /** + * 调用其它程序(exe、bash等可执行程序) + * + */ + openSoftware(args) { + const { softName } = args; + const softwarePath = path.join(getExtraResourcesDir(), softName); + logger.info('[openSoftware] softwarePath:', softwarePath); + + // 检查程序是否存在 + if (!fs.existsSync(softwarePath)) { + return false; + } + // 命令行字符串 并 执行, start 命令后面的路径要加双引号 + const cmdStr = `start "${softwarePath}"`; + exec(cmdStr); + + // 方法二 + // 使用cross模块 + + return true; + } + + /** + * 检测http服务是否开启 + */ + async checkHttpServer() { + const { enable, protocol, host, port } = getConfig().httpServer; + const url = protocol + host + ':' + port; + console.log('[checkHttpServer] url:', url); + const data = { + enable: enable, + server: url + } + return data; + } + + /** + * 一个 http 请求 + * args 是 前端传的参数 + * ctx 是 koa 的 ctx 对象 + */ + async doHttpRequest(args, ctx) { + const httpInfo = { + args, + method: ctx.request.method, + query: ctx.request.query, + body: ctx.request.body + } + logger.info('httpInfo:', httpInfo); + + const { id } = args; + if (!id) { + return false; + } + const dir = electronApp.getPath(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) { + 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; + } + + /** + * 双向异步通信 + */ + ipcSendMsg(args, event) { + const { type, content } = args; + const data = frameworkService.bothWayMessage(type, content, event); + + return data; + } + + /** + * 任务 + */ + someJob(args, event) { + const { jobId, action} = args; + let result; + + switch (action) { + case 'create': + result = frameworkService.doJob(jobId, action, event); + break; + case 'close': + frameworkService.doJob(jobId, action, event); + break; + case 'pause': + frameworkService.doJob(jobId, action, event); + break; + case 'resume': + frameworkService.doJob(jobId, action, event); + break; + default: + } + + let data = { + jobId, + action, + result + } + return data; + } + + /** + * 创建任务池 + */ + async createPool(args, event) { + let num = args.number; + frameworkService.doCreatePool(num, event); + + // test monitor + frameworkService.monitorJob(); + + return; + } + + /** + * 通过进程池执行任务 + */ + someJobByPool(args, event) { + const { jobId, action } = args; + let result; + switch (action) { + case 'run': + result = frameworkService.doJobByPool(jobId, action, event); + break; + default: + } + + let data = { + jobId, + action, + result + } + return data; + } + + /** + * 检查是否有新版本 + */ + checkForUpdater() { + autoUpdaterService.checkUpdate(); + return; + } + + /** + * 下载新版本 + */ + downloadApp() { + autoUpdaterService.download(); + return; + } + + /** + * 测试接口 + */ + hello(args) { + logger.info('hello ', args); + } +} +FrameworkController.toString = () => '[class FrameworkController]'; + +module.exports = FrameworkController; \ No newline at end of file diff --git a/electron/controller/os.js b/electron/controller/os.js new file mode 100644 index 0000000..e49d935 --- /dev/null +++ b/electron/controller/os.js @@ -0,0 +1,175 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const { + app: electronApp, dialog, shell, Notification, +} = require('electron'); +const { windowService } = require('../service/os/window'); + +/** + * example + * @class + */ +class OsController { + + /** + * All methods receive two parameters + * @param args Parameters transmitted by the frontend + * @param event - Event are only available during IPC communication. For details, please refer to the controller documentation + */ + + /** + * Message prompt dialog box + */ + messageShow() { + dialog.showMessageBoxSync({ + type: 'info', // "none", "info", "error", "question" 或者 "warning" + title: 'Custom Title', + message: 'Customize message content', + detail: 'Other additional information' + }) + + return 'Opened the message box'; + } + + /** + * Message prompt and confirmation dialog box + */ + messageShowConfirm() { + const res = dialog.showMessageBoxSync({ + type: 'info', + title: 'Custom Title', + message: 'Customize message content', + detail: 'Other additional information', + cancelId: 1, // Index of buttons used to cancel dialog boxes + defaultId: 0, // Set default selected button + buttons: ['confirm', 'cancel'], + }) + let data = (res === 0) ? 'click the confirm button' : 'click the cancel button'; + + return data; + } + + /** + * Select Directory + */ + selectFolder() { + const filePaths = dialog.showOpenDialogSync({ + properties: ['openDirectory', 'createDirectory'] + }); + + if (!filePaths) { + return null + } + + return filePaths[0]; + } + + /** + * open directory + */ + 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; + } + + /** + * Select Picture + */ + selectPic() { + const filePaths = dialog.showOpenDialogSync({ + title: 'select pic', + properties: ['openFile'], + filters: [ + { name: 'Images', extensions: ['jpg', 'png', 'gif'] }, + ] + }); + if (!filePaths) { + return null + } + + try { + const data = fs.readFileSync(filePaths[0]); + const pic = 'data:image/jpeg;base64,' + data.toString('base64'); + return pic; + } catch (err) { + console.error(err); + return null; + } + } + + /** + * Open a new window + */ + createWindow(args) { + const wcid = windowService.createWindow(args); + return wcid; + } + + /** + * Get Window contents id + */ + getWCid(args) { + const wcid = windowService.getWCid(args); + return wcid; + } + + /** + * Realize communication between two windows through the transfer of the main process + */ + window1ToWindow2(args, event) { + windowService.communicate(args, event); + return; + } + + /** + * Realize communication between two windows through the transfer of the main process + */ + window2ToWindow1(args, event) { + windowService.communicate(args, event); + return; + } + + /** + * Create system notifications + */ + sendNotification(args, event) { + const { title, subtitle, body, silent} = args; + + if (!Notification.isSupported()) { + return '当前系统不支持通知'; + } + + let options = {}; + if (!title) { + options.title = title; + } + if (!subtitle) { + options.subtitle = subtitle; + } + if (!body) { + options.body = body; + } + if (!silent) { + options.silent = silent; + } + windowService.createNotification(options, event); + + return true + } +} +OsController.toString = () => '[class OsController]'; + +module.exports = OsController; \ No newline at end of file diff --git a/electron/jobs/example/hello.js b/electron/jobs/example/hello.js new file mode 100644 index 0000000..a4b0250 --- /dev/null +++ b/electron/jobs/example/hello.js @@ -0,0 +1,11 @@ +'use strict'; + +const { logger } = require('ee-core/log'); + +function welcome() { + logger.info('[child-process] [jobs/example/hello] welcome ! '); +} + +module.exports = { + welcome +}; \ No newline at end of file diff --git a/electron/jobs/example/timer.js b/electron/jobs/example/timer.js new file mode 100644 index 0000000..ed70364 --- /dev/null +++ b/electron/jobs/example/timer.js @@ -0,0 +1,88 @@ +'use strict'; + +const { logger } = require('ee-core/log'); +const { isChildJob, exit } = require('ee-core/ps'); +const { childMessage } = require('ee-core/message'); +const { welcome } = require('./hello'); +const { UserService } = require('../../service/job/user'); + +/** + * example - TimerJob + * @class + */ +class TimerJob { + + constructor(params) { + this.params = params; + this.timer = undefined; + this.timeoutTimer = undefined; + this.number = 0; + this.countdown = 10; // 倒计时 + } + + /** + * handle()方法是必要的,且会被自动调用 + */ + async handle () { + logger.info("[child-process] TimerJob params: ", this.params); + const { jobId } = this.params; + + // 子进程中使用service + // 1. 确保引入的 service 中不能有electron 的 api或依赖, electron 不支持 + const userService = new UserService(); + userService.hello('job'); + + // 执行任务 + this.doTimer(jobId); + } + + /** + * 暂停任务运行 + */ + async pause(jobId) { + logger.info("[child-process] Pause timerJob, jobId: ", jobId); + clearInterval(this.timer); + clearInterval(this.timeoutTimer); + } + + /** + * 恢复任务运行 + */ + async resume(jobId, pid) { + logger.info("[child-process] Resume timerJob, jobId: ", jobId, ", pid: ", pid); + this.doTimer(jobId); + } + + /** + * 运行任务 + */ + async doTimer(jobId) { + // 计时器模拟任务 + const eventName = 'job-timer-progress-' + jobId; + this.timer = setInterval(() => { + welcome(); + + childMessage.send(eventName, {jobId, number: this.number, end: false}); + this.number++; + this.countdown--; + }, 1000); + + // 用 setTimeout 模拟任务运行时长 + this.timeoutTimer = setTimeout(() => { + // 关闭计时器模拟任务 + clearInterval(this.timer); + + // 任务结束,重置前端显示 + childMessage.send(eventName, {jobId, number:0, pid:0, end: true}); + + // 如果是childJob任务,必须调用 exit() 方法,让进程退出,否则会常驻内存 + // 如果是childPoolJob任务,常驻内存,等待下一个业务 + if (isChildJob()) { + exit(); + } + }, this.countdown * 1000) + } +} + +TimerJob.toString = () => '[class TimerJob]'; +module.exports = TimerJob; diff --git a/electron/main.js b/electron/main.js new file mode 100644 index 0000000..b005896 --- /dev/null +++ b/electron/main.js @@ -0,0 +1,19 @@ +const { ElectronEgg } = require('ee-core'); +const { Lifecycle } = require('./preload/lifecycle'); +const { preload } = require('./preload'); + +// new app +const app = new ElectronEgg(); + +// register lifecycle +const life = new Lifecycle(); +app.register("ready", life.ready); +app.register("electron-app-ready", life.electronAppReady); +app.register("window-ready", life.windowReady); +app.register("before-close", life.beforeClose); + +// register preload +app.register("preload", preload); + +// run +app.run(); \ No newline at end of file diff --git a/electron/preload/bridge.js b/electron/preload/bridge.js new file mode 100644 index 0000000..c9b86db --- /dev/null +++ b/electron/preload/bridge.js @@ -0,0 +1,10 @@ +/* + * 如果启用了上下文隔离,渲染进程无法使用electron的api, + * 可通过contextBridge 导出api给渲染进程使用 + */ + +const { contextBridge, ipcRenderer } = require('electron') + +contextBridge.exposeInMainWorld('electron', { + ipcRenderer: ipcRenderer, +}) \ No newline at end of file diff --git a/electron/preload/index.js b/electron/preload/index.js new file mode 100644 index 0000000..6794b52 --- /dev/null +++ b/electron/preload/index.js @@ -0,0 +1,23 @@ +/************************************************* + ** preload为预加载模块,该文件将会在程序启动时加载 ** + *************************************************/ + +const { logger } = require('ee-core/log'); +const { trayService } = require('../service/os/tray'); +const { securityService } = require('../service/os/security'); +const { autoUpdaterService } = require('../service/os/auto_updater'); + +function preload() { + // 示例功能模块,可选择性使用和修改 + logger.info('[preload] load 5'); + trayService.create(); + securityService.create(); + autoUpdaterService.create(); +} + +/** +* 预加载模块入口 +*/ +module.exports = { + preload +} \ No newline at end of file diff --git a/electron/preload/lifecycle.js b/electron/preload/lifecycle.js new file mode 100644 index 0000000..a18a2cc --- /dev/null +++ b/electron/preload/lifecycle.js @@ -0,0 +1,50 @@ +'use strict'; + +const { logger } = require('ee-core/log'); +const { getConfig } = require('ee-core/config'); +const { getMainWindow } = require('ee-core/electron'); + +class Lifecycle { + + /** + * core app have been loaded + */ + async ready() { + logger.info('[lifecycle] ready'); + } + + /** + * electron app ready + */ + async electronAppReady() { + logger.info('[lifecycle] electron-app-ready'); + } + + /** + * main window have been loaded + */ + async windowReady() { + logger.info('[lifecycle] window-ready'); + // 延迟加载,无白屏 + const { windowsOption } = getConfig(); + if (windowsOption.show == false) { + const win = getMainWindow(); + win.once('ready-to-show', () => { + win.show(); + win.focus(); + }) + } + } + + /** + * before app close + */ + async beforeClose() { + logger.info('[lifecycle] before-close'); + } +} +Lifecycle.toString = () => '[class Lifecycle]'; + +module.exports = { + Lifecycle +}; \ No newline at end of file diff --git a/electron/service/cross.js b/electron/service/cross.js new file mode 100644 index 0000000..3e41faa --- /dev/null +++ b/electron/service/cross.js @@ -0,0 +1,151 @@ +'use strict'; + +const { logger } = require('ee-core/log'); +const { getExtraResourcesDir, getLogDir } = require('ee-core/ps'); +const path = require("path"); +const axios = require('axios'); +const { is } = require('ee-core/utils'); +const { cross } = require('ee-core/cross'); + +/** + * cross + * @class + */ +class CrossService { + + info() { + const pids = cross.getPids(); + logger.info('cross pids:', pids); + + let num = 1; + pids.forEach(pid => { + let entity = cross.getProc(pid); + logger.info(`server-${num} name:${entity.name}`); + logger.info(`server-${num} config:`, entity.config); + num++; + }) + + return 'hello electron-egg'; + } + + getUrl(name) { + const serverUrl = cross.getUrl(name); + return serverUrl; + } + + killServer(type, name) { + if (type == 'all') { + cross.killAll(); + } else { + cross.killByName(name); + } + } + + /** + * 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=${getLogDir()}`, `${jarPath}`], + appExit: false, + } + if (is.macOS()) { + // Setup Java program + opt.cmd = path.join(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; + } + + async requestApi(name, urlPath, params) { + const serverUrl = cross.getUrl(name); + const apiHello = serverUrl + urlPath; + console.log('Server Url:', serverUrl); + + const response = await axios({ + method: 'get', + url: apiHello, + timeout: 1000, + params, + proxy: false, + }); + if (response.status == 200) { + const { data } = response; + return data; + } + + return null; + } +} +CrossService.toString = () => '[class CrossService]'; + +module.exports = { + CrossService, + crossService: new CrossService() +}; \ No newline at end of file diff --git a/electron/service/database/basedb.js b/electron/service/database/basedb.js new file mode 100644 index 0000000..4cd0815 --- /dev/null +++ b/electron/service/database/basedb.js @@ -0,0 +1,52 @@ +'use strict'; + +const { SqliteStorage } = require('ee-core/storage'); +const { getDataDir } = require('ee-core/ps'); +const path = require('path'); + +/** + * sqlite数据存储 + * @class + */ +class BasedbService { + + constructor(options) { + const { dbname } = options; + this.dbname = dbname; + this.db = undefined; + this._init(); + } + + /* + * 初始化 + */ + _init() { + // 定义数据文件 + const dbFile = path.join(getDataDir(), "db", this.dbname); + const sqliteOptions = { + timeout: 6000, + verbose: console.log + } + this.storage = new SqliteStorage(dbFile, sqliteOptions); + this.db = this.storage.db; + } + + /* + * change data dir (sqlite) + */ + changeDataDir(dir) { + // the absolute path of the db file + const dbFile = path.join(dir, this.dbname); + const sqliteOptions = { + timeout: 6000, + verbose: console.log + } + this.storage = new SqliteStorage(dbFile, sqliteOptions); + this.db = this.storage.db; + } +} +BasedbService.toString = () => '[class BasedbService]'; + +module.exports = { + BasedbService, +}; \ 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..35bcff4 --- /dev/null +++ b/electron/service/database/sqlitedb.js @@ -0,0 +1,111 @@ +'use strict'; + +const { BasedbService } = require('./basedb'); + +/** + * sqlite数据存储 + * @class + */ +class SqlitedbService extends BasedbService { + + constructor () { + const options = { + dbname: 'sqlite-demo.db', + } + super(options); + this.userTableName = 'user'; + this._initTable(); + } + + /* + * 初始化表 + */ + _initTable() { + // 检查表是否存在 + const masterStmt = this.db.prepare('SELECT * FROM sqlite_master WHERE type=? AND name = ?'); + let tableExists = masterStmt.get('table', this.userTableName); + if (!tableExists) { + // 创建表 + const create_user_table_sql = + `CREATE TABLE ${this.userTableName} + ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name CHAR(50) NOT NULL, + age INT + );` + this.db.exec(create_user_table_sql); + } + } + + /* + * 增 Test data (sqlite) + */ + async addTestDataSqlite(data) { + const insert = this.db.prepare(`INSERT INTO ${this.userTableName} (name, age) VALUES (@name, @age)`); + insert.run(data); + return true; + } + + /* + * 删 Test data (sqlite) + */ + async delTestDataSqlite(name = '') { + const delUser = this.db.prepare(`DELETE FROM ${this.userTableName} WHERE name = ?`); + delUser.run(name); + return true; + } + + /* + * 改 Test data (sqlite) + */ + async updateTestDataSqlite(name= '', age = 0) { + const updateUser = this.db.prepare(`UPDATE ${this.userTableName} SET age = ? WHERE name = ?`); + updateUser.run(age, name); + return true; + } + + /* + * 查 Test data (sqlite) + */ + async getTestDataSqlite(age = 0) { + const selectUser = this.db.prepare(`SELECT * FROM ${this.userTableName} WHERE age = @age`); + const users = selectUser.all({age: age}); + return users; + } + + /* + * all Test data (sqlite) + */ + async getAllTestDataSqlite() { + const selectAllUser = this.db.prepare(`SELECT * FROM ${this.userTableName} `); + const allUser = selectAllUser.all(); + return allUser; + } + + /* + * get data dir (sqlite) + */ + async getDataDir() { + const dir = this.storage.getDbDir(); + return dir; + } + + /* + * set custom data dir (sqlite) + */ + async setCustomDataDir(dir) { + if (!dir) { + return; + } + + this.changeDataDir(dir); + this._initTable(); + 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..3d8627d --- /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 new file mode 100644 index 0000000..cf55fb6 --- /dev/null +++ b/electron/service/example.js @@ -0,0 +1,30 @@ +'use strict'; + +const { logger } = require('ee-core/log'); + +/** + * 示例服务 + * @class + */ +class ExampleService { + + /** + * test + */ + async test(args) { + let obj = { + status:'ok', + params: args + } + + logger.info('ExampleService obj:', obj); + + return obj; + } +} +ExampleService.toString = () => '[class ExampleService]'; + +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..7b98f5b --- /dev/null +++ b/electron/service/framework.js @@ -0,0 +1,163 @@ +'use strict'; + +const { logger } = require('ee-core/log'); +const { ChildJob, ChildPoolJob } = require('ee-core/jobs'); + +/** + * 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..991c0f9 --- /dev/null +++ b/electron/service/job/user.js @@ -0,0 +1,26 @@ +'use strict'; + +const { logger } = require('ee-core/log'); + +// The service used in the job should not rely on Electron's API, as it may cause errors +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/auto_updater.js b/electron/service/os/auto_updater.js new file mode 100644 index 0000000..6ec739c --- /dev/null +++ b/electron/service/os/auto_updater.js @@ -0,0 +1,168 @@ +const { app: electronApp } = require('electron'); +const { autoUpdater } = require("electron-updater"); +const { is } = require('ee-core/utils'); +const { logger } = require('ee-core/log'); +const { getMainWindow, setCloseAndQuit } = require('ee-core/electron'); + +/** + * 自动升级 + * @class + */ +class AutoUpdaterService { + constructor() { + this.config = { + windows: false, + macOS: false, + linux: false, + options: { + provider: 'generic', + url: 'http://kodo.qiniu.com/' + }, + } + } + + /** + * 创建 + */ + create () { + logger.info('[autoUpdater] load'); + const cfg = this.config; + if ((is.windows() && cfg.windows) || (is.macOS() && cfg.macOS) || (is.linux() && cfg.linux)) { + // continue + } else { + return + } + + const status = { + error: -1, + available: 1, + noAvailable: 2, + downloading: 3, + downloaded: 4, + } + + const version = electronApp.getVersion(); + logger.info('[autoUpdater] current version: ', version); + + // 设置下载服务器地址 + let server = cfg.options.url; + let lastChar = server.substring(server.length - 1); + server = lastChar === '/' ? server : server + "/"; + cfg.options.url = server; + + try { + autoUpdater.setFeedURL(cfg.options); + } catch (error) { + logger.error('[autoUpdater] setFeedURL error : ', error); + } + + autoUpdater.on('checking-for-update', () => { + //sendStatusToWindow('正在检查更新...'); + }) + autoUpdater.on('update-available', () => { + const data = { + status: status.available, + desc: '有可用更新' + } + this.sendStatusToWindow(data); + }) + autoUpdater.on('update-not-available', () => { + const data = { + status: status.noAvailable, + desc: '没有可用更新' + } + this.sendStatusToWindow(data); + }) + autoUpdater.on('error', (err) => { + const data = { + status: status.error, + desc: err + } + this.sendStatusToWindow(data); + }) + autoUpdater.on('download-progress', (progressObj) => { + const percentNumber = parseInt(progressObj.percent); + const totalSize = this.bytesChange(progressObj.total); + const transferredSize = this.bytesChange(progressObj.transferred); + let text = '已下载 ' + percentNumber + '%'; + text = text + ' (' + transferredSize + "/" + totalSize + ')'; + + const data = { + status: status.downloading, + desc: text, + percentNumber, + totalSize, + transferredSize + } + logger.info('[autoUpdater] progress: ', text); + this.sendStatusToWindow(data); + }) + autoUpdater.on('update-downloaded', () => { + const data = { + status: status.downloaded, + desc: '下载完成' + } + this.sendStatusToWindow(data); + + // 托盘插件里面设置了阻止窗口关闭,这里设置允许关闭窗口 + setCloseAndQuit(true); + + // Install updates and exit the application + autoUpdater.quitAndInstall(); + }); + } + + /** + * 检查更新 + */ + checkUpdate () { + autoUpdater.checkForUpdates(); + } + + /** + * 下载更新 + */ + download () { + autoUpdater.downloadUpdate(); + } + + /** + * 向前端发消息 + */ + sendStatusToWindow(content = {}) { + const textJson = JSON.stringify(content); + const channel = 'custom/app/updater'; + const win = getMainWindow(); + win.webContents.send(channel, textJson); + } + + /** + * 单位转换 + */ + bytesChange (limit) { + let size = ""; + if(limit < 0.1 * 1024){ + size = limit.toFixed(2) + "B"; + }else if(limit < 0.1 * 1024 * 1024){ + size = (limit/1024).toFixed(2) + "KB"; + }else if(limit < 0.1 * 1024 * 1024 * 1024){ + size = (limit/(1024 * 1024)).toFixed(2) + "MB"; + }else{ + size = (limit/(1024 * 1024 * 1024)).toFixed(2) + "GB"; + } + + let sizeStr = size + ""; + let index = sizeStr.indexOf("."); + let dou = sizeStr.substring(index + 1 , index + 3); + if(dou == "00"){ + return sizeStr.substring(0, index) + sizeStr.substring(index + 3, index + 5); + } + + return size; + } +} +AutoUpdaterService.toString = () => '[class AutoUpdaterService]'; + +module.exports = { + autoUpdaterService: new AutoUpdaterService() +}; \ No newline at end of file diff --git a/electron/service/os/security.js b/electron/service/os/security.js new file mode 100644 index 0000000..002f9d0 --- /dev/null +++ b/electron/service/os/security.js @@ -0,0 +1,33 @@ +'use strict'; + +const { logger } = require('ee-core/log'); +const { app: electronApp } = require('electron'); + +/** + * 安全 + * @class + */ +class SecurityService { + + /** + * 创建 + */ + create () { + logger.info('[security] load'); + const runWithDebug = process.argv.find(function(e){ + let isHasDebug = e.includes("--inspect") || e.includes("--inspect-brk") || e.includes("--remote-debugging-port"); + return isHasDebug; + }) + + // 不允许远程调试 + if (runWithDebug) { + logger.error('[error] Remote debugging is not allowed, runWithDebug:', runWithDebug); + electronApp.quit(); + } + } +} +SecurityService.toString = () => '[class SecurityService]'; + +module.exports = { + securityService: new SecurityService() +}; \ No newline at end of file diff --git a/electron/service/os/tray.js b/electron/service/os/tray.js new file mode 100644 index 0000000..39bed02 --- /dev/null +++ b/electron/service/os/tray.js @@ -0,0 +1,75 @@ +const { Tray, Menu } = require('electron'); +const path = require('path'); +const { getBaseDir } = require('ee-core/ps'); +const { logger } = require('ee-core/log'); +const { app: electronApp } = require('electron'); +const { getMainWindow, getCloseAndQuit, setCloseAndQuit } = require('ee-core/electron'); + +/** + * 托盘 + * @class + */ +class TrayService { + + constructor() { + this.tray = null; + this.config = { + title: 'electron-egg', + icon: '/public/images/tray.png' + } + } + + /** + * 创建托盘 + */ + create () { + logger.info('[tray] load'); + + const cfg = this.config; + const mainWindow = getMainWindow(); + + // tray icon + const iconPath = path.join(getBaseDir(), cfg.icon); + + // 托盘菜单功能列表 + const trayMenuTemplate = [ + { + label: '显示', + click: function () { + mainWindow.show(); + } + }, + { + label: '退出', + click: function () { + electronApp.quit(); + } + } + ] + + // 设置一个标识,点击关闭,最小化到托盘 + setCloseAndQuit(false); + mainWindow.on('close', (event) => { + if (getCloseAndQuit()) { + return; + } + mainWindow.hide(); + event.preventDefault(); + }); + + // 实例化托盘 + this.tray = new Tray(iconPath); + this.tray.setToolTip(cfg.title); + const contextMenu = Menu.buildFromTemplate(trayMenuTemplate); + this.tray.setContextMenu(contextMenu); + // 左键单击的时候能够显示主窗口 + this.tray.on('click', () => { + mainWindow.show() + }) + } +} +TrayService.toString = () => '[class TrayService]'; + +module.exports = { + trayService: new TrayService() +}; \ No newline at end of file diff --git a/electron/service/os/window.js b/electron/service/os/window.js new file mode 100644 index 0000000..c00c710 --- /dev/null +++ b/electron/service/os/window.js @@ -0,0 +1,134 @@ +'use strict'; + +const path = require('path'); +const { BrowserWindow, Notification } = require('electron'); +const { getMainWindow } = require('ee-core/electron'); +const { isProd, getBaseDir } = require('ee-core/ps'); +const { getConfig } = require('ee-core/config'); +const { isFileProtocol } = require('ee-core/utils'); +const { logger } = require('ee-core/log'); + +/** + * Window + * @class + */ +class WindowService { + + constructor() { + this.myNotification = null; + this.windows = {} + } + + /** + * Create a new window + */ + createWindow(args) { + const { type, content, windowName, windowTitle } = args; + let contentUrl = null; + if (type == 'html') { + contentUrl = path.join('file://', getBaseDir(), content) + } else if (type == 'web') { + contentUrl = content; + } else if (type == 'vue') { + let addr = 'http://localhost:8080' + if (isProd()) { + const { mainServer } = getConfig(); + if (isFileProtocol(mainServer.protocol)) { + addr = mainServer.protocol + path.join(getBaseDir(), mainServer.indexPath); + } else { + addr = mainServer.protocol + mainServer.host + ':' + mainServer.port; + } + } + + contentUrl = addr + content; + } else { + // some + } + + logger.info('[createWindow] url: ', contentUrl); + const opt = { + title: windowTitle, + x: 10, + y: 10, + width: 980, + height: 650, + webPreferences: { + contextIsolation: false, + nodeIntegration: true, + }, + } + const win = new BrowserWindow(opt); + const winContentsId = win.webContents.id; + win.loadURL(contentUrl); + win.webContents.openDevTools(); + this.windows[windowName] = win; + + return winContentsId; + } + + /** + * Get window contents id + */ + getWCid(args) { + const { windowName } = args; + let win; + if (windowName == 'main') { + win = getMainWindow(); + } else { + win = this.windows[windowName]; + } + + return win.webContents.id; + } + + /** + * Realize communication between two windows through the transfer of the main process + */ + communicate(args) { + const { receiver, content } = args; + if (receiver == 'main') { + const win = getMainWindow(); + win.webContents.send('controller/os/window2ToWindow1', content); + } else if (receiver == 'window2') { + const win = this.windows[receiver]; + win.webContents.send('controller/os/window1ToWindow2', content); + } + } + + /** + * 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(); + } + +} +WindowService.toString = () => '[class WindowService]'; + +module.exports = { + WindowService, + windowService: new WindowService() +}; \ No newline at end of file diff --git a/frontend/.editorconfig b/frontend/.editorconfig new file mode 100644 index 0000000..3454886 --- /dev/null +++ b/frontend/.editorconfig @@ -0,0 +1,14 @@ +# https://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +insert_final_newline = false +trim_trailing_whitespace = false diff --git a/frontend/.env.development b/frontend/.env.development new file mode 100644 index 0000000..9005571 --- /dev/null +++ b/frontend/.env.development @@ -0,0 +1,2 @@ +VITE_TITLE="" +VITE_GO_URL="http://localhost:8081" \ No newline at end of file diff --git a/frontend/.env.production b/frontend/.env.production new file mode 100644 index 0000000..a0eaf9f --- /dev/null +++ b/frontend/.env.production @@ -0,0 +1,2 @@ +VITE_TITLE="" +VITE_GO_URL="http://www.test.com" \ No newline at end of file diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..082d756 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,6 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local +package-lock.json \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..c0b96bd --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,105 @@ + + + + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + diff --git a/frontend/jsconfig.json b/frontend/jsconfig.json new file mode 100644 index 0000000..ee3163b --- /dev/null +++ b/frontend/jsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + // "allowSyntheticDefaultImports": true, + "baseUrl": "./", + "paths": { + "@/*": [ + "src/*" + ], + } + }, + "exclude": [ + "node_modules", + "dist" + ] +} \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..764f728 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,33 @@ +{ + "name": "ee", + "version": "4.0.0", + "scripts": { + "dev": "vite --host --port 8080", + "serve": "vite --host --port 8080", + "build-staging": "vite build --mode staging", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@ant-design/icons-vue": "^6.1.0", + "ant-design-vue": "^3.2.20", + "axios": "^0.21.1", + "pinia": "^2.2.6", + "socket.io-client": "^4.4.1", + "store2": "^2.13.2", + "vue": "^3.5.12", + "vue-router": "^4.0.14", + "vuex": "^4.0.2" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^4.2.3", + "@vue/compiler-sfc": "^3.2.33", + "less": "^4.1.2", + "less-loader": "^10.2.0", + "postcss": "^8.4.13", + "postcss-pxtorem": "^6.0.0", + "terser": "^5.19.1", + "vite": "^5.4.11", + "vite-plugin-compression": "^0.5.1" + } +} diff --git a/frontend/src/App.vue b/frontend/src/App.vue new file mode 100644 index 0000000..f9684e6 --- /dev/null +++ b/frontend/src/App.vue @@ -0,0 +1,14 @@ + + + diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js new file mode 100644 index 0000000..73a707c --- /dev/null +++ b/frontend/src/api/index.js @@ -0,0 +1,85 @@ + +/** + * Definition of communication channel between main process and rendering process + * separator: "/" | "." ; (Please check the config file properties: channelSeparator) + * format:controller/filename/method | controller.filename.method + * Definition of communication channels between main process and rendering process + */ +const ipcApiRoute = { + example: { + test: 'controller/example/test', + }, + framework: { + checkForUpdater: 'controller/framework/checkForUpdater', + downloadApp: 'controller/framework/downloadApp', + jsondbOperation: 'controller/framework/jsondbOperation', + sqlitedbOperation: 'controller/framework/sqlitedbOperation', + uploadFile: 'controller/framework/uploadFile', + checkHttpServer: 'controller/framework/checkHttpServer', + doHttpRequest: 'controller/framework/doHttpRequest', + doSocketRequest: 'controller/framework/doSocketRequest', + ipcInvokeMsg: 'controller/framework/ipcInvokeMsg', + ipcSendSyncMsg: 'controller/framework/ipcSendSyncMsg', + ipcSendMsg: 'controller/framework/ipcSendMsg', + startJavaServer: 'controller/framework/startJavaServer', + closeJavaServer: 'controller/framework/closeJavaServer', + someJob: 'controller/framework/someJob', + timerJobProgress: 'controller/framework/timerJobProgress', + createPool: 'controller/framework/createPool', + createPoolNotice: 'controller/framework/createPoolNotice', + someJobByPool: 'controller/framework/someJobByPool', + hello: 'controller/framework/hello', + openSoftware: 'controller/framework/openSoftware', + }, + + // os + os: { + messageShow: 'controller/os/messageShow', + messageShowConfirm: 'controller/os/messageShowConfirm', + selectFolder: 'controller/os/selectFolder', + selectPic: 'controller/os/selectPic', + openDirectory: 'controller/os/openDirectory', + loadViewContent: 'controller/os/loadViewContent', + removeViewContent: 'controller/os/removeViewContent', + createWindow: 'controller/os/createWindow', + getWCid: 'controller/os/getWCid', + sendNotification: 'controller/os/sendNotification', + initPowerMonitor: 'controller/os/initPowerMonitor', + getScreen: 'controller/os/getScreen', + autoLaunch: 'controller/os/autoLaunch', + setTheme: 'controller/os/setTheme', + getTheme: 'controller/os/getTheme', + window1ToWindow2: 'controller/os/window1ToWindow2', + window2ToWindow1: 'controller/os/window2ToWindow1', + }, + + // effect + effect: { + selectFile: 'controller/effect/selectFile', + loginWindow: 'controller/effect/loginWindow', + restoreWindow: 'controller/effect/restoreWindow', + }, + + // cross + cross: { + crossInfo: 'controller/cross/info', + getCrossUrl: 'controller/cross/getUrl', + killCrossServer: 'controller/cross/killServer', + createCrossServer: 'controller/cross/createServer', + requestApi: 'controller/cross/requestApi', + } +} + +/** + * Customize Channel + * Format: Custom (recommended to add a prefix) + */ +const specialIpcRoute = { + appUpdater: 'custom/app/updater', // updater channel +} + +export { + ipcApiRoute, + specialIpcRoute +} + diff --git a/frontend/src/assets/global.less b/frontend/src/assets/global.less new file mode 100644 index 0000000..4192318 --- /dev/null +++ b/frontend/src/assets/global.less @@ -0,0 +1,16 @@ +#app { + font-family: Avenir, Helvetica, Arial, sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-align: center; + color: #2c3e50; + height: 100%; +} + +/* 滚动条 */ +::-webkit-scrollbar{width:8px;height:4px} +::-webkit-scrollbar-button{width:10px;height:0} +::-webkit-scrollbar-track{background:0 0} +::-webkit-scrollbar-thumb{background:#E6FFEE;-webkit-transition:.3s;transition:.3s} +::-webkit-scrollbar-thumb:hover{background-color:#07C160} +::-webkit-scrollbar-thumb:active{background-color:#07C160} \ No newline at end of file diff --git a/frontend/src/assets/login.png b/frontend/src/assets/login.png new file mode 100644 index 0000000..5882c86 Binary files /dev/null and b/frontend/src/assets/login.png differ diff --git a/frontend/src/assets/logo.png b/frontend/src/assets/logo.png new file mode 100644 index 0000000..95dc60b Binary files /dev/null and b/frontend/src/assets/logo.png differ diff --git a/frontend/src/assets/theme.less b/frontend/src/assets/theme.less new file mode 100644 index 0000000..8d9b773 --- /dev/null +++ b/frontend/src/assets/theme.less @@ -0,0 +1,17 @@ +@import 'ant-design-vue/dist/antd.less'; + +// 可自定义主题颜色 +@primary-color: #07C160; // 全局主色 +@link-color: #1890ff; // 链接色 +@success-color: #52c41a; // 成功色 +@warning-color: #faad14; // 警告色 +@error-color: #f5222d; // 错误色 +@font-size-base: 14px; // 主字号 +@heading-color: rgba(0, 0, 0, 0.85); // 标题色 +@text-color: rgba(0, 0, 0, 0.65); // 主文本色 +@text-color-secondary: rgba(0, 0, 0, 0.45); // 次文本色 +@disabled-color: rgba(0, 0, 0, 0.25); // 失效色 +@border-radius-base: 4px; // 组件/浮层圆角 +@border-color-base: #dce3e8; // 边框色 +@box-shadow-base: 0 2px 8px rgba(0, 0, 0, 0.15); // 浮层阴影 + diff --git a/frontend/src/components/global/iconFont.js b/frontend/src/components/global/iconFont.js new file mode 100644 index 0000000..6ee51f0 --- /dev/null +++ b/frontend/src/components/global/iconFont.js @@ -0,0 +1,18 @@ +import { createFromIconfontCN } from '@ant-design/icons-vue' +import { h } from 'vue' + +const IconFont = createFromIconfontCN({ + scriptUrl: 'https://at.alicdn.com/t/font_2456157_4ovzopz659q.js', + extraCommonProps: { + type: 'icon-fengche', + style: { + fontSize: '18px', + }, + }, +}) + +const DynamicIconFont = props => { + return h(IconFont, { type: props.type || 'icon-fengche' }) +} + +export default DynamicIconFont diff --git a/frontend/src/components/global/index.js b/frontend/src/components/global/index.js new file mode 100644 index 0000000..35e4bf5 --- /dev/null +++ b/frontend/src/components/global/index.js @@ -0,0 +1,12 @@ +import iconFont from './iconFont' +const modules = import.meta.glob('./*.vue', { eager: true }) +const map = {} +Object.keys(modules).forEach(file => { + const modulesName = file.replace('./', '').replace('.vue', '') + map[modulesName] = modules[file].default +}) +const globalComponents = { + ...map, + iconFont, +} +export default globalComponents diff --git a/frontend/src/layouts/AppSider.vue b/frontend/src/layouts/AppSider.vue new file mode 100644 index 0000000..a9ff2b4 --- /dev/null +++ b/frontend/src/layouts/AppSider.vue @@ -0,0 +1,111 @@ + + + diff --git a/frontend/src/layouts/Menu.vue b/frontend/src/layouts/Menu.vue new file mode 100644 index 0000000..d327c5a --- /dev/null +++ b/frontend/src/layouts/Menu.vue @@ -0,0 +1,75 @@ + + + diff --git a/frontend/src/layouts/index.js b/frontend/src/layouts/index.js new file mode 100644 index 0000000..7ae5f46 --- /dev/null +++ b/frontend/src/layouts/index.js @@ -0,0 +1,7 @@ +import AppSider from '@/layouts/AppSider' +import Menu from '@/layouts/Menu' + +export { + AppSider, + Menu +} diff --git a/frontend/src/main.js b/frontend/src/main.js new file mode 100644 index 0000000..908ad1e --- /dev/null +++ b/frontend/src/main.js @@ -0,0 +1,26 @@ +import * as AntIcon from '@ant-design/icons-vue'; +import Antd from 'ant-design-vue'; +import { createApp } from 'vue'; +import App from './App.vue'; +import './assets/global.less'; +import './assets/theme.less'; +import components from './components/global'; +import Router from './router/index'; + +const app = createApp(App) +app.config.productionTip = false + +// components +for (const i in components) { + app.component(i, components[i]) +} + +// icon +for (const i in AntIcon) { + const whiteList = ['createFromIconfontCN', 'getTwoToneColor', 'setTwoToneColor', 'default'] + if (!whiteList.includes(i)) { + app.component(i, AntIcon[i]) + } +} + +app.use(Antd).use(Router).mount('#app') diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js new file mode 100644 index 0000000..e69c70f --- /dev/null +++ b/frontend/src/router/index.js @@ -0,0 +1,9 @@ +import { createRouter, createWebHashHistory } from 'vue-router' +import routerMap from './routerMap' + +const Router = createRouter({ + history: createWebHashHistory(), + routes: routerMap, +}) + +export default Router diff --git a/frontend/src/router/routerMap.js b/frontend/src/router/routerMap.js new file mode 100644 index 0000000..3d647d6 --- /dev/null +++ b/frontend/src/router/routerMap.js @@ -0,0 +1,142 @@ +/** + * 基础路由 + * @type { *[] } + */ + +const constantRouterMap = [ + { + path: '/', + component: () => import('@/layouts/AppSider.vue'), + children: [ + { + path: '/framework', + name: 'Framework', + component: () => import('@/layouts/Menu.vue'), + props: { id: 'framework' }, + //props: true, + redirect: { name: 'FrameworkSocketIpc' }, + children: [ + { + path: '/framework/socket/ipc', + name: 'FrameworkSocketIpc', + component: () => import('@/views/framework/socket/Ipc.vue') + }, + { + path: '/framework/socket/httpserver', + name: 'FrameworkSocketHttpServer', + component: () => import('@/views/framework/socket/HttpServer.vue') + }, + { + path: '/framework/socket/socketserver', + name: 'FrameworkSocketSocketServer', + component: () => import('@/views/framework/socket/SocketServer.vue') + }, + { + path: '/framework/sqlitedb/index', + name: 'FrameworkSqliteDBIndex', + component: () => import('@/views/framework/sqlitedb/Index.vue') + }, + { + path: '/framework/jobs/index', + name: 'FrameworkJobsIndex', + component: () => import('@/views/framework/jobs/Index.vue') + }, + { + path: '/framework/software/index', + name: 'FrameworkSoftwareIndex', + component: () => import('@/views/framework/software/Index.vue') + }, + { + path: '/framework/updater/index', + name: 'FrameworkUpdaterIndex', + component: () => import('@/views/framework/updater/Index.vue') + }, + ] + }, + { + path: '/os', + name: 'Os', + component: () => import('@/layouts/Menu.vue'), + props: { id: 'os' }, + redirect: { name: 'OsFileIndex' }, + children: [ + { + path: '/os/file/index', + name: 'OsFileIndex', + component: () => import('@/views/os/file/Index.vue') + }, + { + path: '/os/file/pic', + name: 'OsFilePic', + component: () => import('@/views/os/file/Pic.vue') + }, + { + path: '/os/window/index', + name: 'OsWindowIndex', + component: () => import('@/views/os/window/Index.vue') + }, + { + path: '/os/notification/index', + name: 'OsNotificationIndex', + component: () => import('@/views/os/notification/Index.vue') + } + ] + }, + { + path: '/effect', + name: 'Effect', + component: () => import('@/layouts/Menu.vue'), + props: { id: 'effect' }, + redirect: { name: 'EffectLoginIndex' }, + children: [ + { + path: '/effect/login/index', + name: 'EffectLoginIndex', + component: () => import('@/views/effect/login/Index.vue') + } + ] + }, + { + path: '/cross', + name: 'Cross', + component: () => import('@/layouts/Menu.vue'), + props: { id: 'cross' }, + redirect: { name: 'CrossGoIndex' }, + children: [ + { + path: '/cross/go/index', + name: 'CrossGoIndex', + component: () => import('@/views/cross/go/Index.vue') + }, + { + path: '/cross/java/index', + name: 'CrossJavaIndex', + component: () => import('@/views/cross/java/Index.vue') + }, + { + path: '/cross/python/index', + name: 'CrossPythonIndex', + component: () => import('@/views/cross/python/Index.vue') + }, + ] + }, + ] + }, + { + path: '/special', + children: [ + { + path: 'subwindow', + name: 'SpecialSubwindowIpc', + component: () => import('@/views/os/subwindow/Ipc.vue') + }, + { + path: '/login', + name: 'SpecialLoginWindow', + component: () => import('@/views/effect/login/Window.vue') + }, + ] + }, +] + +export default constantRouterMap \ No newline at end of file diff --git a/frontend/src/router/subMenu.js b/frontend/src/router/subMenu.js new file mode 100644 index 0000000..9c5b462 --- /dev/null +++ b/frontend/src/router/subMenu.js @@ -0,0 +1,101 @@ +// 子菜单 +export default { + framework: { + 'menu_100' : { + icon: 'profile', + title: '通信', + pageName: 'FrameworkSocketIpc', + params: {} + }, + 'menu_101' : { + icon: 'profile', + title: 'http服务', + pageName: 'FrameworkSocketHttpServer', + params: {} + }, + 'menu_102' : { + icon: 'profile', + title: 'socket服务', + pageName: 'FrameworkSocketSocketServer', + params: {} + }, + 'menu_104' : { + icon: 'profile', + title: 'sqlite数据库', + pageName: 'FrameworkSqliteDBIndex', + params: {} + }, + 'menu_105' : { + icon: 'profile', + title: '任务', + pageName: 'FrameworkJobsIndex', + params: {} + }, + 'menu_106' : { + icon: 'profile', + title: '软件调用', + pageName: 'FrameworkSoftwareIndex', + params: {} + }, + 'menu_107' : { + icon: 'profile', + title: '自动更新', + pageName: 'FrameworkUpdaterIndex', + params: {} + }, + }, + os: { + 'menu_100' : { + icon: 'profile', + title: '文件', + pageName: 'OsFileIndex', + params: {} + }, + 'menu_102' : { + icon: 'profile', + title: '窗口', + pageName: 'OsWindowIndex', + params: {} + }, + 'menu_103' : { + icon: 'profile', + title: '桌面通知', + pageName: 'OsNotificationIndex', + params: {} + }, + 'menu_110' : { + icon: 'profile', + title: '图片', + pageName: 'OsFilePic', + params: {} + }, + }, + effect: { + 'menu_100' : { + icon: 'profile', + title: '登录', + pageName: 'EffectLoginIndex', + params: {} + } + }, + cross: { + 'menu_100' : { + icon: 'profile', + title: 'go服务', + pageName: 'CrossGoIndex', + params: {} + }, + 'menu_110' : { + icon: 'profile', + title: 'java服务', + pageName: 'CrossJavaIndex', + params: {} + }, + 'menu_120' : { + icon: 'profile', + title: 'python服务', + pageName: 'CrossPythonIndex', + params: {} + }, + }, +} diff --git a/frontend/src/utils/iconList.js b/frontend/src/utils/iconList.js new file mode 100644 index 0000000..e8597aa --- /dev/null +++ b/frontend/src/utils/iconList.js @@ -0,0 +1,27 @@ +export default [ + { name: '对话框', type: 'icon-duihuakuang' }, + { name: '闹钟', type: 'icon-naozhong' }, + { name: '笑脸', type: 'icon-xiaolian' }, + { name: 'ok', type: 'icon-ok' }, + { name: '风车', type: 'icon-fengche' }, + { name: '汗颜', type: 'icon-hanyan' }, + { name: '相机', type: 'icon-xiangji' }, + { name: '礼物', type: 'icon-liwu' }, + { name: '礼花', type: 'icon-lihua' }, + { name: '扭蛋', type: 'icon-niudan' }, + { name: '流星', type: 'icon-liuxing' }, + { name: '风筝', type: 'icon-fengzheng' }, + { name: '蛋糕', type: 'icon-dangao' }, + { name: '泡泡', type: 'icon-paopao' }, + { name: '购物', type: 'icon-gouwu' }, + { name: '饮料', type: 'icon-yinliao' }, + { name: '云彩', type: 'icon-yuncai' }, + { name: '彩铅', type: 'icon-caiqian' }, + { name: '纸飞机', type: 'icon-zhifeiji' }, + { name: '点赞', type: 'icon-dianzan' }, + { name: '煎蛋', type: 'icon-jiandan' }, + { name: '小熊', type: 'icon-xiaoxiong' }, + { name: '花', type: 'icon-hua' }, + { name: '眼睛', type: 'icon-yanjing' }, +] + \ No newline at end of file diff --git a/frontend/src/utils/ipcRenderer.js b/frontend/src/utils/ipcRenderer.js new file mode 100644 index 0000000..b229b36 --- /dev/null +++ b/frontend/src/utils/ipcRenderer.js @@ -0,0 +1,33 @@ +const Renderer = (window.require && window.require('electron')) || window.electron || {}; + +/** + * ipc + * 官方api说明:https://www.electronjs.org/zh/docs/latest/api/ipc-renderer + * + * 属性/方法 + * ipc.invoke(channel, param) - 发送异步消息(invoke/handle 模型) + * ipc.sendSync(channel, param) - 发送同步消息(send/on 模型) + * ipc.on(channel, listener) - 监听 channel, 当新消息到达,调用 listener + * ipc.once(channel, listener) - 添加一次性 listener 函数 + * ipc.removeListener(channel, listener) - 为特定的 channel 从监听队列中删除特定的 listener 监听者 + * ipc.removeAllListeners(channel) - 移除所有的监听器,当指定 channel 时只移除与其相关的所有监听器 + * ipc.send(channel, ...args) - 通过channel向主进程发送异步消息 + * ipc.postMessage(channel, message, [transfer]) - 发送消息到主进程 + * ipc.sendTo(webContentsId, channel, ...args) - 通过 channel 发送消息到带有 webContentsId 的窗口 + * ipc.sendToHost(channel, ...args) - 消息会被发送到 host 页面上的 元素 + */ + +/** + * ipc + */ +const ipc = Renderer.ipcRenderer || undefined; + +/** + * 是否为EE环境 + */ +const isEE = ipc ? true : false; + +export { + Renderer, ipc, isEE +}; + diff --git a/frontend/src/views/cross/go/Index.vue b/frontend/src/views/cross/go/Index.vue new file mode 100644 index 0000000..57c0aa3 --- /dev/null +++ b/frontend/src/views/cross/go/Index.vue @@ -0,0 +1,104 @@ + + + diff --git a/frontend/src/views/cross/java/Index.vue b/frontend/src/views/cross/java/Index.vue new file mode 100644 index 0000000..30cca35 --- /dev/null +++ b/frontend/src/views/cross/java/Index.vue @@ -0,0 +1,102 @@ + + + + \ No newline at end of file diff --git a/frontend/src/views/cross/python/Index.vue b/frontend/src/views/cross/python/Index.vue new file mode 100644 index 0000000..1983c55 --- /dev/null +++ b/frontend/src/views/cross/python/Index.vue @@ -0,0 +1,101 @@ + + + + \ No newline at end of file diff --git a/frontend/src/views/effect/login/Index.vue b/frontend/src/views/effect/login/Index.vue new file mode 100644 index 0000000..d956bfc --- /dev/null +++ b/frontend/src/views/effect/login/Index.vue @@ -0,0 +1,41 @@ + + + + \ No newline at end of file diff --git a/frontend/src/views/effect/login/Window.vue b/frontend/src/views/effect/login/Window.vue new file mode 100644 index 0000000..f63f0d9 --- /dev/null +++ b/frontend/src/views/effect/login/Window.vue @@ -0,0 +1,45 @@ + + + + \ No newline at end of file diff --git a/frontend/src/views/framework/jobs/Index.vue b/frontend/src/views/framework/jobs/Index.vue new file mode 100644 index 0000000..3fbcb62 --- /dev/null +++ b/frontend/src/views/framework/jobs/Index.vue @@ -0,0 +1,187 @@ + + + diff --git a/frontend/src/views/framework/socket/HttpServer.vue b/frontend/src/views/framework/socket/HttpServer.vue new file mode 100644 index 0000000..e182eb3 --- /dev/null +++ b/frontend/src/views/framework/socket/HttpServer.vue @@ -0,0 +1,114 @@ + + + diff --git a/frontend/src/views/framework/socket/Ipc.vue b/frontend/src/views/framework/socket/Ipc.vue new file mode 100644 index 0000000..ec192f1 --- /dev/null +++ b/frontend/src/views/framework/socket/Ipc.vue @@ -0,0 +1,157 @@ + + + diff --git a/frontend/src/views/framework/socket/SocketServer.vue b/frontend/src/views/framework/socket/SocketServer.vue new file mode 100644 index 0000000..beb0321 --- /dev/null +++ b/frontend/src/views/framework/socket/SocketServer.vue @@ -0,0 +1,76 @@ + + + diff --git a/frontend/src/views/framework/software/Index.vue b/frontend/src/views/framework/software/Index.vue new file mode 100644 index 0000000..063a536 --- /dev/null +++ b/frontend/src/views/framework/software/Index.vue @@ -0,0 +1,52 @@ + + + diff --git a/frontend/src/views/framework/sqlitedb/Index.vue b/frontend/src/views/framework/sqlitedb/Index.vue new file mode 100644 index 0000000..6f033a7 --- /dev/null +++ b/frontend/src/views/framework/sqlitedb/Index.vue @@ -0,0 +1,278 @@ + + + diff --git a/frontend/src/views/framework/updater/Index.vue b/frontend/src/views/framework/updater/Index.vue new file mode 100644 index 0000000..3917cf2 --- /dev/null +++ b/frontend/src/views/framework/updater/Index.vue @@ -0,0 +1,85 @@ + + + + \ No newline at end of file diff --git a/frontend/src/views/os/file/Index.vue b/frontend/src/views/os/file/Index.vue new file mode 100644 index 0000000..06b6386 --- /dev/null +++ b/frontend/src/views/os/file/Index.vue @@ -0,0 +1,120 @@ + + + diff --git a/frontend/src/views/os/file/Pic.vue b/frontend/src/views/os/file/Pic.vue new file mode 100644 index 0000000..efd58b3 --- /dev/null +++ b/frontend/src/views/os/file/Pic.vue @@ -0,0 +1,47 @@ + + + + \ No newline at end of file diff --git a/frontend/src/views/os/notification/Index.vue b/frontend/src/views/os/notification/Index.vue new file mode 100644 index 0000000..160130b --- /dev/null +++ b/frontend/src/views/os/notification/Index.vue @@ -0,0 +1,86 @@ + + + diff --git a/frontend/src/views/os/subwindow/Ipc.vue b/frontend/src/views/os/subwindow/Ipc.vue new file mode 100644 index 0000000..db40c3c --- /dev/null +++ b/frontend/src/views/os/subwindow/Ipc.vue @@ -0,0 +1,143 @@ + + + diff --git a/frontend/src/views/os/window/Index.vue b/frontend/src/views/os/window/Index.vue new file mode 100644 index 0000000..9f8192e --- /dev/null +++ b/frontend/src/views/os/window/Index.vue @@ -0,0 +1,77 @@ + + + diff --git a/frontend/vite.config.js b/frontend/vite.config.js new file mode 100644 index 0000000..e7b7e05 --- /dev/null +++ b/frontend/vite.config.js @@ -0,0 +1,57 @@ +import vue from '@vitejs/plugin-vue' +import { defineConfig } from 'vite' +import viteCompression from 'vite-plugin-compression' + +import path from 'path' +// https://vitejs.dev/config/ +export default defineConfig(({ command, mode }) => { + return { + // 项目插件 + plugins: [ + vue(), + viteCompression({ + verbose: true, + disable: false, + threshold: 1025, + algorithm: 'gzip', + ext: '.gz', + }), + ], + // 基础配置 + base: './', + publicDir: 'public', + resolve: { + alias: { + '@': path.resolve(__dirname, 'src'), + }, + }, + css: { + preprocessorOptions: { + less: { + modifyVars: { + '@border-color-base': '#dce3e8', + }, + javascriptEnabled: true, + }, + }, + }, + build: { + outDir: 'dist', + assetsDir: 'assets', + assetsInlineLimit: 4096, + cssCodeSplit: true, + brotliSize: false, + sourcemap: false, + minify: 'terser', + terserOptions: { + compress: { + // 生产环境去除console及debug + drop_console: false, + drop_debugger: true, + }, + }, + }, + } +}) + + diff --git a/go/api/example.go b/go/api/example.go new file mode 100644 index 0000000..0e4f63e --- /dev/null +++ b/go/api/example.go @@ -0,0 +1,66 @@ +package api + +import ( + "net/http" + + "github.com/wallace5303/ee-go/eapp" + "github.com/wallace5303/ee-go/ehelper" + "github.com/wallace5303/ee-go/ehttp/router" + "github.com/wallace5303/ee-go/elog" + + "github.com/gin-gonic/gin" + //"electron-egg/demo/sql/sqlitelib" +) + +// 使用 router Ctx +func Hello(c *router.Ctx) { + ret := ehelper.GetJson() + defer c.JSON(ret) + + ret.Data = "hello electron-egg" + elog.Logger.Info(" print Hello ") +} + +// 使用 gin Context +func Info(gc *gin.Context) { + ret := ehelper.GetJson() + defer gc.JSON(http.StatusOK, ret) + + elog.Logger.Info(" print info ") +} + +func Exit(c *router.Ctx) { + ret := ehelper.GetJson() + defer c.JSON(ret) + + eapp.Close() +} + +func SetValue(c *router.Ctx) { + ret := ehelper.GetJson() + defer c.JSON(ret) + + // arg, ok := c.ArgJson() + // if !ok { + // return + // } + + // keyName := arg["key"].(string) + // vallue := arg["value"] + + // sqlitelib.SetStatData(keyName, vallue) +} + +func GetValue(c *router.Ctx) { + ret := ehelper.GetJson() + defer c.JSON(ret) + + // arg, ok := c.ArgJson() + // if !ok { + // return + // } + + // keyName := arg["key"].(string) + + // ret.Data = sqlitelib.GetStat(keyName) +} diff --git a/go/config/.air.toml b/go/config/.air.toml new file mode 100644 index 0000000..f0e1444 --- /dev/null +++ b/go/config/.air.toml @@ -0,0 +1,61 @@ +root = "." +tmp_dir = "tmp" + +[build] +# 编译使用的shell命令 +cmd = "go build -o ./tmp/goapp ./main.go" + +# 由`cmd`命令得到的二进制文件名 +bin = "./tmp/goapp --basedir=../ --env=dev --port=7073" +# 在运行二进制文件时添加额外的参数 (bin/full_bin)。将运行“./tmp/main hello world” +# args_bin = ["hello", "world"] + +# 如果文件更改过于频繁,则没有必要在每次更改时都触发构建。可以设置触发构建的延迟时间/毫秒 +delay = 1000 + +# 忽略(不监听)文件的扩展名或目录 +exclude_dir = ["tmp", "public"] + +# 监听指定目录的文件 +# include_dir = [] + +# 监听扩展名的文件 +include_ext = ["go", "mod", "sum", "json", "tpl", "tmpl", "html"] + +# 忽略(不监听)指定文件 +exclude_file = [] + +# 忽略符合通过正则匹配到的文件 +exclude_regex = [] + +# 忽略未进行修改的文件 +exclude_unchanged = true + +# 按照目录的符号链接 +follow_symlink = false + +# 杀死进程前发送中断信号(Windows不支持) +send_interrupt = true +# 发送中断信号后延迟时间/毫秒 +kill_delay = 2000 + +# 发生构建错误时,停止运行旧的二进制文件 +stop_on_error = true + +# 这个日志文件放在你的`tmp_dir`中 +log = "air.log" + +[log] +# 显示日志时间 +time = true + +[color] +# 自定义每个部分的颜色。如果未找到颜色,请使用原始应用程序日志。 +main = "magenta" +watcher = "cyan" +build = "yellow" +runner = "green" + +[misc] +# 退出时删除 tmp 目录 +clean_on_exit = true \ No newline at end of file diff --git a/go/config/.air.windows.toml b/go/config/.air.windows.toml new file mode 100644 index 0000000..a8b85d2 --- /dev/null +++ b/go/config/.air.windows.toml @@ -0,0 +1,61 @@ +root = "." +tmp_dir = "tmp" + +[build] +# 编译使用的shell命令 +cmd = "go build -o ./tmp/goapp.exe ./main.go" + +# 由`cmd`命令得到的二进制文件名 +bin = "./tmp/goapp.exe --basedir=../ --env=dev --port=7073" +# 在运行二进制文件时添加额外的参数 (bin/full_bin)。将运行“./tmp/main hello world” +# args_bin = ["hello", "world"] + +# 如果文件更改过于频繁,则没有必要在每次更改时都触发构建。可以设置触发构建的延迟时间/毫秒 +delay = 1000 + +# 忽略(不监听)文件的扩展名或目录 +exclude_dir = ["tmp", "public"] + +# 监听指定目录的文件 +# include_dir = [] + +# 监听扩展名的文件 +include_ext = ["go", "mod", "sum", "json", "tpl", "tmpl", "html"] + +# 忽略(不监听)指定文件 +exclude_file = [] + +# 忽略符合通过正则匹配到的文件 +exclude_regex = [] + +# 忽略未进行修改的文件 +exclude_unchanged = true + +# 按照目录的符号链接 +follow_symlink = false + +# 杀死进程前发送中断信号(Windows不支持) +send_interrupt = true +# 发送中断信号后延迟时间/毫秒 +kill_delay = 2000 + +# 发生构建错误时,停止运行旧的二进制文件 +stop_on_error = true + +# 这个日志文件放在你的`tmp_dir`中 +log = "air.log" + +[log] +# 显示日志时间 +time = true + +[color] +# 自定义每个部分的颜色。如果未找到颜色,请使用原始应用程序日志。 +main = "magenta" +watcher = "cyan" +build = "yellow" +runner = "green" + +[misc] +# 退出时删除 tmp 目录 +clean_on_exit = true \ No newline at end of file diff --git a/go/config/config.default.json b/go/config/config.default.json new file mode 100644 index 0000000..6132dca --- /dev/null +++ b/go/config/config.default.json @@ -0,0 +1,27 @@ +{ + "logger": { + "output_json": false, + "level": "info", + "filename": "ee-go.log", + "max_size": 1024, + "max_age": 10 + }, + "core_logger": { + "output_json": false, + "level": "info", + "filename": "ee-go-core.log", + "max_size": 1024, + "max_age": 10 + }, + "http": { + "enable": true, + "port": 7073, + "network": false + }, + "static": { + "enable": true, + "package": "public/package.json", + "config": "public/config", + "dist": "public/dist" + } +} \ No newline at end of file diff --git a/go/config/config.local.json b/go/config/config.local.json new file mode 100644 index 0000000..2268fe0 --- /dev/null +++ b/go/config/config.local.json @@ -0,0 +1,9 @@ +{ + "logger": { + "output_json": false, + "level": "info", + "filename": "ee-go.log", + "max_size": 1024, + "max_age": 20 + } +} \ No newline at end of file diff --git a/go/config/config.prod.json b/go/config/config.prod.json new file mode 100644 index 0000000..d2b0aec --- /dev/null +++ b/go/config/config.prod.json @@ -0,0 +1,9 @@ +{ + "logger": { + "output_json": false, + "level": "info", + "filename": "ee-go.log", + "max_size": 1024, + "max_age": 30 + } +} \ No newline at end of file diff --git a/go/demo/index.go b/go/demo/index.go new file mode 100644 index 0000000..8c92980 --- /dev/null +++ b/go/demo/index.go @@ -0,0 +1,26 @@ +package demo + +import ( + "electron-egg/demo/job" + //"electron-egg/demo/sql/sqlitelib" + "electron-egg/demo/util" + + "github.com/wallace5303/ee-go/eapp" + "github.com/wallace5303/ee-go/elog" +) + +// 使用 router Ctx +func Index() { + elog.Logger.Info("Start Demo") + + // 初始化基础数据 + util.Boot() + // 初始化数据库 + //sqlitelib.InitDB(false) + // 初始化任务 + job.Boot() + // 注册关闭前的处理函数 + eapp.Register("beforeClose", func() { + //sqlitelib.CloseDatabase() + }) +} diff --git a/go/demo/job/index.go b/go/demo/job/index.go new file mode 100644 index 0000000..5339007 --- /dev/null +++ b/go/demo/job/index.go @@ -0,0 +1,37 @@ +package job + +import ( + "time" + + "github.com/wallace5303/ee-go/elog" + "github.com/wallace5303/ee-go/eruntime" + "github.com/wallace5303/ee-go/etask" + "github.com/wallace5303/ee-go/eutil" +) + +var ( + checkStatusInterval = 5 +) + +func Boot() { + if eruntime.IsDev() { + checkStatusInterval = 2 + } + go etask.Every(1000*time.Millisecond, etask.ExecTask) + go etask.Every(time.Duration(checkStatusInterval)*time.Second, etask.Status) + + // test task + AddTestTask() +} + +func AddTestTask() { + count := 10 + for i := 0; i < count; i++ { + etask.AddTask("task.demo", hello) + } +} + +func hello() { + defer eutil.Recover() + elog.Logger.Info("[task] hello") +} diff --git a/go/demo/sql/sqlitelib/sql.go b/go/demo/sql/sqlitelib/sql.go new file mode 100644 index 0000000..6adb921 --- /dev/null +++ b/go/demo/sql/sqlitelib/sql.go @@ -0,0 +1,208 @@ +package sqlitelib + +import ( + "database/sql" + "errors" + "os" + "runtime" + "runtime/debug" + "strings" + "sync" + "time" + + "electron-egg/demo/util" + + "github.com/wallace5303/ee-go/ehelper" + "github.com/wallace5303/ee-go/elog" + + _ "github.com/glebarez/go-sqlite" +) + +var ( + db *sql.DB +) + +var initDBLock = sync.Mutex{} + +func InitDB(forceRebuild bool) (err error) { + initDBLock.Lock() + defer initDBLock.Unlock() + + elog.Logger.Infof("Init database") + initDBConnection() + + if !forceRebuild { + // 检查数据库结构版本,如果版本不一致的话说明改过表结构,需要重建 + dbVer := GetDatabaseVer() + if util.DatabaseVer == dbVer { + return + } + + elog.Logger.Infof("rebuilding database ......") + } + + // 不存在库或者版本不一致都会走到这里 + CloseDatabase() + if ehelper.FileIsExist(util.DBPath) { + if err = removeDatabaseFile(); err != nil { + elog.Logger.Errorf("remove database file [%s] failed: %s", util.DBPath, err) + + err = nil + } + } + + initDBConnection() + initDBTables() + + return +} + +func initDBConnection() { + if db != nil { + CloseDatabase() + } + + dsn := util.DBPath + "?_pragma=busy_timeout(7000)" + + "&_pragma=journal_mode(WAL)" + + "&_pragma=synchronous(1)" + + "&_pragma=mmap_size(2684354560)" + + "&_pragma=cache_size(-20480)" + + "&_pragma=page_size(32768)" + + "&_pragma=case_sensitive_like(OFF)" + + var err error + db, err = sql.Open("sqlite", dsn) + if err != nil { + elog.Logger.Errorf("create database failed: %s", err) + } + elog.Logger.Infof("DB data source name: %s", dsn) + + db.SetMaxIdleConns(20) + db.SetMaxOpenConns(20) + db.SetConnMaxLifetime(365 * 24 * time.Hour) +} + +func initDBTables() { + elog.Logger.Infof("init tables ......") + + _, err := db.Exec("DROP TABLE IF EXISTS stat") + if err != nil { + elog.Logger.Errorf("drop table [stat] failed: %s", err) + } + _, err = db.Exec("CREATE TABLE stat ( key varchar ( 255 ) NOT NULL DEFAULT '', value text DEFAULT '', PRIMARY KEY ( key ) );") + if err != nil { + elog.Logger.Errorf("create table [stat] failed: %s", err) + } + + _, err = db.Exec("DROP TABLE IF EXISTS storage") + if err != nil { + elog.Logger.Errorf("drop table [storage] failed: %s", err) + } + _, err = db.Exec("CREATE TABLE storage ( name varchar ( 255 ), value text, expires_time datetime );") + if err != nil { + elog.Logger.Errorf("create table [storage] failed: %s", err) + } + _, err = db.Exec("CREATE UNIQUE INDEX index_name ON storage ( name ASC );") + if err != nil { + elog.Logger.Errorf("create INDEX [index_name] failed: %s", err) + } + + setDatabaseVer() +} + +func CloseDatabase() (err error) { + if db == nil { + return + } + + err = db.Close() + debug.FreeOSMemory() + runtime.GC() + return +} + +func removeDatabaseFile() (err error) { + err = os.RemoveAll(util.DBPath) + if err != nil { + return + } + err = os.RemoveAll(util.DBPath + "-shm") + if err != nil { + return + } + err = os.RemoveAll(util.DBPath + "-wal") + if err != nil { + return + } + return +} + +func beginTx() (tx *sql.Tx, err error) { + if tx, err = db.Begin(); err != nil { + elog.Logger.Errorf("begin tx failed: %s\n", err) + if strings.Contains(err.Error(), "database is locked") { + os.Exit(1) + } + } + return +} + +func commitTx(tx *sql.Tx) (err error) { + if tx == nil { + elog.Logger.Errorf("tx is nil") + return errors.New("tx is nil") + } + + if err = tx.Commit(); err != nil { + elog.Logger.Errorf("commit tx failed: %s\n", err) + } + return +} + +func execStmtTx(tx *sql.Tx, stmt string, args ...interface{}) (err error) { + elog.Logger.Infof("[sql/sqlitelib/execStmtTx] sql: %s args: %+v", stmt, args) + + if _, err = tx.Exec(stmt, args...); err != nil { + if strings.Contains(err.Error(), "database disk image is malformed") { + tx.Rollback() + CloseDatabase() + elog.Logger.Errorf("database disk image [%s] is malformed, please restart SiYuan kernel to rebuild it", util.DBPath) + } + elog.Logger.Errorf("exec database stmt [%s] failed: %s\n %s", stmt) + return + } + return +} + +func prepareExecInsertTx(tx *sql.Tx, stmtSQL string, args []interface{}) (err error) { + stmt, err := tx.Prepare(stmtSQL) + + if err != nil { + return + } + + if _, err = stmt.Exec(args...); err != nil { + elog.Logger.Errorf("exec database stmt [%s] failed: %s", stmtSQL, err) + return + } + return +} + +func queryRow(query string, args ...interface{}) *sql.Row { + elog.Logger.Infof("[sql/sqlitelib/queryRow] sql: %s args: %+v", query, args) + query = strings.TrimSpace(query) + if query == "" { + elog.Logger.Errorf("statement is empty") + return nil + } + return db.QueryRow(query, args...) +} + +func query(query string, args ...interface{}) (*sql.Rows, error) { + elog.Logger.Infof("[sql/sqlitelib/query] sql: %s args: %+v", query, args) + query = strings.TrimSpace(query) + if query == "" { + return nil, errors.New("statement is empty") + } + return db.Query(query, args...) +} diff --git a/go/demo/sql/sqlitelib/stat.go b/go/demo/sql/sqlitelib/stat.go new file mode 100644 index 0000000..dc5c608 --- /dev/null +++ b/go/demo/sql/sqlitelib/stat.go @@ -0,0 +1,84 @@ +package sqlitelib + +import ( + "database/sql" + "fmt" + "strings" + + "electron-egg/demo/util" + + "github.com/wallace5303/ee-go/elog" +) + +type Stat struct { + Key string `json:"key"` + Val string `json:"value"` +} + +const ( + dbVersionName = "duola_database_version" +) + +func GetDatabaseVer() (ret string) { + stmt := "SELECT value FROM stat WHERE `key` = '" + dbVersionName + "'" + row := db.QueryRow(stmt) + if err := row.Scan(&ret); nil != err { + if !strings.Contains(err.Error(), "no such table") { + elog.Logger.Errorf("query database version failed: %s", err) + } + } + + return +} + +func setDatabaseVer() { + key := dbVersionName + tx, err := beginTx() + if err != nil { + return + } + if err = PutStat(tx, key, util.DatabaseVer); err != nil { + return + } + commitTx(tx) +} + +func PutStat(tx *sql.Tx, key, value string) (err error) { + stmt := "DELETE FROM stat WHERE `key` = '" + key + "'" + if err = execStmtTx(tx, stmt); err != nil { + return + } + + stmt = "INSERT INTO stat VALUES ('" + key + "', '" + value + "')" + err = execStmtTx(tx, stmt) + return +} + +func GetStat(key string) (ret string) { + stmt := "SELECT value FROM stat WHERE `key` = '" + key + "'" + row := queryRow(stmt) + row.Scan(&ret) + return +} + +func SetStatData(key string, value any) (err error) { + tx, bErr := beginTx() + if bErr != nil { + fmt.Printf("SetStatData bErr %v\n", bErr) + return + } + + stmt := "DELETE FROM stat WHERE `key` = '" + key + "'" + if err = execStmtTx(tx, stmt); err != nil { + return + } + + stmt = "INSERT INTO stat (key, value) VALUES (?, ?)" + err = execStmtTx(tx, stmt, key, value) + if err != nil { + return + } + + commitTx(tx) + return +} diff --git a/go/demo/util/index.go b/go/demo/util/index.go new file mode 100644 index 0000000..77b2fef --- /dev/null +++ b/go/demo/util/index.go @@ -0,0 +1,58 @@ +package util + +import ( + "os" + "path/filepath" + + "github.com/wallace5303/ee-go/elog" + "github.com/wallace5303/ee-go/eruntime" +) + +// 基础 +const ( + Version = "0.1.0" + LocalHost = "127.0.0.1" // 伺服地址 + + // DatabaseVer 数据库版本 + DatabaseVer = "20240101" +) + +// 初始化 +var ( + DBName = "ee.db" + DBPath string // SQLite 数据库文件路径 + + ConfDir string // 用户配置目录路径 + LocalDir string // 用户数据目录路径 + DBDir string // 用户DB目录路径 + TmpDir string // 用户临时目录路径 +) + +func Boot() { + initPathDir() +} + +func initPathDir() { + ConfDir = filepath.Join(eruntime.DataDir, "conf") + LocalDir = filepath.Join(eruntime.DataDir, "local") + DBDir = filepath.Join(eruntime.DataDir, "db") + DBPath = filepath.Join(DBDir, DBName) + TmpDir = eruntime.TmpDir + // 创建 + createDir() +} + +func createDir() { + if err := os.MkdirAll(ConfDir, 0755); nil != err && !os.IsExist(err) { + elog.Logger.Errorf("create conf folder [%s] failed: %s", ConfDir, err) + } + if err := os.MkdirAll(LocalDir, 0755); nil != err && !os.IsExist(err) { + elog.Logger.Errorf("create data folder [%s] failed: %s", LocalDir, err) + } + if err := os.MkdirAll(TmpDir, 0755); nil != err && !os.IsExist(err) { + elog.Logger.Errorf("create tmp folder [%s] failed: %s", TmpDir, err) + } + if err := os.MkdirAll(DBDir, 0755); nil != err && !os.IsExist(err) { + elog.Logger.Errorf("create db folder [%s] failed: %s", DBDir, err) + } +} diff --git a/go/go.mod b/go/go.mod new file mode 100644 index 0000000..b305292 --- /dev/null +++ b/go/go.mod @@ -0,0 +1,75 @@ +module electron-egg + +go 1.20 + +require ( + github.com/gin-gonic/gin v1.9.1 + github.com/glebarez/go-sqlite v1.22.0 + github.com/wallace5303/ee-go v1.2.1 +) + +require ( + github.com/bytedance/sonic v1.9.1 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/gin-contrib/gzip v0.0.6 // indirect + github.com/gin-contrib/sessions v0.0.5 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.14.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/google/uuid v1.5.0 // indirect + github.com/gorilla/context v1.1.1 // indirect + github.com/gorilla/securecookie v1.1.1 // indirect + github.com/gorilla/sessions v1.2.1 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/mssola/useragent v1.0.0 // indirect + github.com/natefinch/lumberjack v2.0.0+incompatible // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/sagikazarmark/locafero v0.3.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/shirou/gopsutil/v3 v3.23.8 // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.10.0 // indirect + github.com/spf13/cast v1.5.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/viper v1.17.0 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.11 // indirect + github.com/yusufpapurcu/wmi v1.2.3 // indirect + go.uber.org/multierr v1.10.0 // indirect + go.uber.org/zap v1.26.0 // indirect + golang.org/x/arch v0.3.0 // indirect + golang.org/x/crypto v0.14.0 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/net v0.16.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/text v0.13.0 // indirect + google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + modernc.org/libc v1.37.6 // indirect + modernc.org/mathutil v1.6.0 // indirect + modernc.org/memory v1.7.2 // indirect + modernc.org/sqlite v1.28.0 // indirect +) diff --git a/go/main.go b/go/main.go new file mode 100644 index 0000000..1afaa83 --- /dev/null +++ b/go/main.go @@ -0,0 +1,29 @@ +package main + +import ( + "embed" + + "github.com/wallace5303/ee-go/eboot" + + "electron-egg/demo" + "electron-egg/router" +) + +var ( + //go:embed public/** + staticFS embed.FS +) + +func main() { + // Initialize ee-go + ego := eboot.New(staticFS) + + // User business logic + router.Api() + + // demo + demo.Index() + + // ee-go runtime + ego.Run() +} diff --git a/go/router/router.go b/go/router/router.go new file mode 100644 index 0000000..d57977a --- /dev/null +++ b/go/router/router.go @@ -0,0 +1,19 @@ +package router + +import ( + "electron-egg/api" + + eRouter "github.com/wallace5303/ee-go/ehttp/router" +) + +func Api() { + + // 注册路由 + eRouter.Handle("GET", "/api/hello", api.Hello) + eRouter.Handle("GET", "/api/exit", api.Exit) + eRouter.Handle("GET", "/api/getValue", api.GetValue) + eRouter.Handle("POST", "/api/setValue", api.SetValue) + + // 使用 gin 注册路由 + eRouter.GinRouter.GET("/api/info", api.Info) +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..269c061 --- /dev/null +++ b/package.json @@ -0,0 +1,63 @@ +{ + "name": "ee-next", + "version": "4.0.0", + "description": "A fast, desktop software development framework", + "main": "./public/electron/main.js", + "scripts": { + "dev": "ee-bin dev", + "build": "npm run build-frontend && npm run build-electron && ee-bin encrypt", + "start": "ee-bin start", + "dev-frontend": "ee-bin dev --serve=frontend", + "dev-electron": "ee-bin dev --serve=electron", + "dev-go": "ee-bin exec --cmds=go", + "dev-go-w": "ee-bin exec --cmds=go_w", + "dev-python": "ee-bin exec --cmds=python", + "build-frontend": "ee-bin build --cmds=frontend && ee-bin move --flag=frontend_dist", + "build-electron": "ee-bin build --cmds=electron", + "build-go-w": "ee-bin move --flag=go_static,go_config,go_package && ee-bin build --cmds=go_w", + "build-go-m": "ee-bin move --flag=go_static,go_config,go_package,go_images && ee-bin build --cmds=go_m", + "build-go-l": "ee-bin move --flag=go_static,go_config,go_package,go_images && ee-bin build --cmds=go_l", + "build-python": "ee-bin build --cmds=python && ee-bin move --flag=python_dist", + "encrypt": "ee-bin encrypt", + "icon": "ee-bin icon", + "re-sqlite": "electron-rebuild -f -w better-sqlite3", + "build-w": "ee-bin build --cmds=win64", + "build-we": "ee-bin build --cmds=win_e", + "build-m": "ee-bin build --cmds=mac", + "build-m-arm64": "ee-bin build --cmds=mac_arm64", + "build-l": "ee-bin build --cmds=linux", + "debug-dev": "cross-env DEBUG=ee-* ee-bin dev", + "debug-encrypt": "ee-bin encrypt", + "debug-electron": "cross-env DEBUG=ee-* ee-bin dev --serve=electron", + "debug-move": "ee-bin move --flag=frontend_dist" + }, + "repository": "https://github.com/dromara/electron-egg.git", + "keywords": [ + "Electron", + "electron-egg", + "ElectronEgg" + ], + "author": "哆啦好梦, Inc <530353222@qq.com>", + "license": "Apache", + "devDependencies": { + "@electron-toolkit/tsconfig": "^1.0.1", + "@electron/rebuild": "^3.7.1", + "@types/better-sqlite3": "^7.6.12", + "@types/node": "^22.10.2", + "chokidar": "^4.0.3", + "cross-env": "^7.0.3", + "debug": "^4.4.0", + "ee-bin": "file:.yalc/ee-bin", + "electron": "^31.7.6", + "electron-builder": "^25.1.8", + "esbuild": "0.24.2", + "typescript": "^5.4.2" + }, + "dependencies": { + "axios": "^1.7.9", + "better-sqlite3": "^11.7.0", + "dayjs": "^1.11.13", + "ee-core": "file:.yalc/ee-core", + "electron-updater": "^6.3.8" + } +} diff --git a/public/html/loading.html b/public/html/loading.html new file mode 100644 index 0000000..9d1520b --- /dev/null +++ b/public/html/loading.html @@ -0,0 +1,94 @@ + + + + + + + + + +
+
+
+
+
+
+
+
+ + diff --git a/public/html/view_example.html b/public/html/view_example.html new file mode 100644 index 0000000..d480ac7 --- /dev/null +++ b/public/html/view_example.html @@ -0,0 +1,22 @@ + + + + + + + +
+ 这是一个html页面 +
+ + \ No newline at end of file diff --git a/public/images/example/aw-3.png b/public/images/example/aw-3.png new file mode 100644 index 0000000..ddebf47 Binary files /dev/null and b/public/images/example/aw-3.png differ diff --git a/public/images/example/ee-mac-home.png b/public/images/example/ee-mac-home.png new file mode 100644 index 0000000..7fd5093 Binary files /dev/null and b/public/images/example/ee-mac-home.png differ diff --git a/public/images/example/ee-project-7.png b/public/images/example/ee-project-7.png new file mode 100644 index 0000000..adc4a31 Binary files /dev/null and b/public/images/example/ee-project-7.png differ diff --git a/public/images/example/ee-win-home.png b/public/images/example/ee-win-home.png new file mode 100644 index 0000000..5a83862 Binary files /dev/null and b/public/images/example/ee-win-home.png differ diff --git a/public/images/example/ee-zs.png b/public/images/example/ee-zs.png new file mode 100644 index 0000000..ab27baa Binary files /dev/null and b/public/images/example/ee-zs.png differ diff --git a/public/images/example/ee_game_1.png b/public/images/example/ee_game_1.png new file mode 100644 index 0000000..c08dcb7 Binary files /dev/null and b/public/images/example/ee_game_1.png differ diff --git a/public/images/example/fm-p1.png b/public/images/example/fm-p1.png new file mode 100644 index 0000000..45ae065 Binary files /dev/null and b/public/images/example/fm-p1.png differ diff --git a/public/images/example/fm-p2.png b/public/images/example/fm-p2.png new file mode 100644 index 0000000..37360c9 Binary files /dev/null and b/public/images/example/fm-p2.png differ diff --git a/public/images/example/fm-p4.png b/public/images/example/fm-p4.png new file mode 100644 index 0000000..8ed280a Binary files /dev/null and b/public/images/example/fm-p4.png differ diff --git a/public/images/example/im-p1.png b/public/images/example/im-p1.png new file mode 100644 index 0000000..440a47c Binary files /dev/null and b/public/images/example/im-p1.png differ diff --git a/public/images/example/im-p5.png b/public/images/example/im-p5.png new file mode 100644 index 0000000..0b7ac54 Binary files /dev/null and b/public/images/example/im-p5.png differ diff --git a/public/images/example/im2-p1.png b/public/images/example/im2-p1.png new file mode 100644 index 0000000..44d7145 Binary files /dev/null and b/public/images/example/im2-p1.png differ diff --git a/public/images/example/logo.png b/public/images/example/logo.png new file mode 100644 index 0000000..95dc60b Binary files /dev/null and b/public/images/example/logo.png differ diff --git a/public/images/example/lol-zhanji.png b/public/images/example/lol-zhanji.png new file mode 100644 index 0000000..7166c2d Binary files /dev/null and b/public/images/example/lol-zhanji.png differ diff --git a/public/images/example/rq-1.png b/public/images/example/rq-1.png new file mode 100644 index 0000000..aeca28e Binary files /dev/null and b/public/images/example/rq-1.png differ diff --git a/public/images/example/rq-2.png b/public/images/example/rq-2.png new file mode 100644 index 0000000..ec793a1 Binary files /dev/null and b/public/images/example/rq-2.png differ diff --git a/public/images/example/ubuntu-db.png b/public/images/example/ubuntu-db.png new file mode 100644 index 0000000..fbcf44e Binary files /dev/null and b/public/images/example/ubuntu-db.png differ diff --git a/public/images/example/uos-home.png b/public/images/example/uos-home.png new file mode 100644 index 0000000..b20c3dd Binary files /dev/null and b/public/images/example/uos-home.png differ diff --git a/public/images/example/v3-home.png b/public/images/example/v3-home.png new file mode 100644 index 0000000..7535541 Binary files /dev/null and b/public/images/example/v3-home.png differ diff --git a/public/images/example/vue-antd.png b/public/images/example/vue-antd.png new file mode 100644 index 0000000..4747cfc Binary files /dev/null and b/public/images/example/vue-antd.png differ diff --git a/public/images/logo-32.png b/public/images/logo-32.png new file mode 100644 index 0000000..60445f3 Binary files /dev/null and b/public/images/logo-32.png differ diff --git a/public/images/logo.png b/public/images/logo.png new file mode 100644 index 0000000..004feec Binary files /dev/null and b/public/images/logo.png differ diff --git a/public/images/tray.png b/public/images/tray.png new file mode 100644 index 0000000..e88d02d Binary files /dev/null and b/public/images/tray.png differ diff --git a/public/ssl/localhost+1.key b/public/ssl/localhost+1.key new file mode 100644 index 0000000..9a04a92 --- /dev/null +++ b/public/ssl/localhost+1.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDpWYqPkljVtDNp +JVwlcOxNVihQPf4T3Q/tuIt5znV5ImBmlWg+OyuG47Y5e+qPMjFCSX4ebTAtBMbY +m6AJihkKy0iKj1mVS9TPCzqcHFnUliCIqJMKFsJTWygNGgogjXhlxFaJgCZO6Gw6 +ocWp6nw1gMhrMIxqT2MQIQX16SD1IH/F4JMoaYuifnR+OOgbS3yUKHDTFApkZAWn +dS4GpbT39rW9cmbJrGHCvl8bsm8MXMdXars10A++wjjmHbtZu8beFv+zKbDw4iAu +ls8p/lAoIvQy8MkLLM5b392CLMaK0517+qM3VdEZ0ph+32m9IEoj5QV8xgaPdnyP +8/8a2bU/AgMBAAECggEBAIzFZ8GVF+JUA2+7CgvMQ8Gj6E4AF/cDtUhDvGCPHG8n +PeCk4W4pY+jMFnI3PxmDvhOvIlZYqGeAKjUiLTmUBedtGyX7tJ9MT+VXcNQchlSo +/Jd0mr/LWw/OPispOlLJBYjfGRV6KaIQtLnqPcRzoNrmBgIkF5FKswhX47CmIyu8 +eEXmDsqXzWmYwdOHLNDshLOrCgFgRnqK7HjxvNqb9k4qv3V9WxlJYITK7L4eJNcZ +XYvGl+QV1+n153phyYnHcbohzvE44Hv8e4hiY9uYNdWckNBNFLpk9vEmUjewCRT1 +kL30woifUhZCalXBIfKEHNZg23teOMHNZTqcS+ER64ECgYEA7BAWVWS/8b9h1AmP +SYhJgudb+ck1ItuvEDjonKuUCTBYm0iQI7cgAimasIBybRO8zwKstvWGDnUJJEkB +Oh7AXF6CbKuP7navTBAaF3AXsNfDgQuEwtEg78iqiQzTyp6FOvvOT2aOtzi86Ju1 +zwKu+DLV3ETeokHWrQmV1uZnbZMCgYEA/Q7LdEEvZxz8OqOjtj8iKLWAv0WvnHTO +Zjqn/BbXi2PyI8d5ntIUPEgm/MRpvXTa465Uu8Orujfq51Z/oFbyvhhEOWT/sVn/ +fzzbH4xXb3bLJ3D+LILQzsm4d8yrV66Re1ehFVxRPIy5RTpssED7bOns9ds4lsHT +W9p7ibYRBSUCgYAMpJftnuXA1tUwfAqWj5wQTL/aUvJrmYR4w/OBYJcfHt3AA1Tk +9MvcEcpdJaP7P5FfLO9/JQs2/wGsVdSg/kCjMdSeaVneFbExy7L6CmDacdPgt3M2 +0+iFryOjD3LQaUkNbasRCZcfLQTBGIXWPniMhnx5vZ6G5ivPPLIvvktPzQKBgQCd +s/yi5ISwE+Y0fQpnZwzYpdQoXztDm5+NIfzSI0IMgirClWt7yJwHvUdeuuDSyuIm +hdwUb6qzkGl55fP/bnA0e1b5FbIrSlTpbHl6PbG3qyaL2+TqxFNwq1Gkhw44xHex +kDi44SFXRLOpKvHVHYoSo+2igg3QFdasJYpblfUhaQKBgF0l9PpMbDLdPlI33IQz +bEzw0ig8R8nHocJzOkK/BdLI8WiItYGgq4mcZGDWsztNg17QQGTEFrH7H9B8DKAJ +p75jz5O83arjMECqAiXlSGOWtq6NhbgyJcQJxvvvN8wObVFoVkLoEbqE1TkDqZfI +CqiusA5zgG89vzP9xFhW2ia2 +-----END PRIVATE KEY----- diff --git a/public/ssl/localhost+1.pem b/public/ssl/localhost+1.pem new file mode 100644 index 0000000..332a3ae --- /dev/null +++ b/public/ssl/localhost+1.pem @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEXzCCAsegAwIBAgIRAOLUY4uS9d2yXx0vd6qql30wDQYJKoZIhvcNAQELBQAw +gZExHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTEzMDEGA1UECwwqQklM +SUJJTElcZ2Fvc2h1YWl4aW5nQENOMjEwMTAyMjc0ICjljaHor7opMTowOAYDVQQD +DDFta2NlcnQgQklMSUJJTElcZ2Fvc2h1YWl4aW5nQENOMjEwMTAyMjc0ICjljaHo +r7opMB4XDTIyMDcyNzA4NDcyOFoXDTI0MTAyNzA4NDcyOFowXjEnMCUGA1UEChMe +bWtjZXJ0IGRldmVsb3BtZW50IGNlcnRpZmljYXRlMTMwMQYDVQQLDCpCSUxJQklM +SVxnYW9zaHVhaXhpbmdAQ04yMTAxMDIyNzQgKOWNoeivuikwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQDpWYqPkljVtDNpJVwlcOxNVihQPf4T3Q/tuIt5 +znV5ImBmlWg+OyuG47Y5e+qPMjFCSX4ebTAtBMbYm6AJihkKy0iKj1mVS9TPCzqc +HFnUliCIqJMKFsJTWygNGgogjXhlxFaJgCZO6Gw6ocWp6nw1gMhrMIxqT2MQIQX1 +6SD1IH/F4JMoaYuifnR+OOgbS3yUKHDTFApkZAWndS4GpbT39rW9cmbJrGHCvl8b +sm8MXMdXars10A++wjjmHbtZu8beFv+zKbDw4iAuls8p/lAoIvQy8MkLLM5b392C +LMaK0517+qM3VdEZ0ph+32m9IEoj5QV8xgaPdnyP8/8a2bU/AgMBAAGjZDBiMA4G +A1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAfBgNVHSMEGDAWgBQn +B1E5Js/cFhxBwpZL59aoK/skLjAaBgNVHREEEzARgglsb2NhbGhvc3SHBH8AAAEw +DQYJKoZIhvcNAQELBQADggGBAIaUncQj2XN2rNn6sE0MuaWboFqwpkydyei6FvtN +c/TY9RWW3QRYICcO721l/2jBiWplQt/ZYaJ+IWN+C+3JSAz9IYsM/nMgxHL2azLQ +zHKnASEjxptW9+mlsgVk2LTrBfbc197ikLu80M/0jQYaIBeoEOaMlhBjno139nTO +evNheyFKvAhggOseD00I9VBZkKDBxvqr6PHnGjyAU43C1/HkNjglIbQjAZdBmXlX +HuelQ97glfhzyApvmczPrc8IAqPhtYn2nJ5P6Ea35LEc3D7uVExywcjDFcSwMJCb +TXqouzM/U8pO+DGeuvgwkYrBGlA7iEE+ZQgxCBatOXwG95THtFlfW+H0ILHB2tcX +P+Kztwd+4ipPciJz+1NK7z7erwfxHO5hmXJskH9YWi6YJsIw5g1iYs0pJJ/4p7Bd +8qSGEhri/+iijcC76q+1N0xhJxQrDDlC0pKp6oAYFDGKirzwmlAf/eJBy0ORWjCj +yk+d9T622yzcXa5fw3HBZh1o6A== +-----END CERTIFICATE----- diff --git a/python/fastapi-demo.py b/python/fastapi-demo.py new file mode 100644 index 0000000..586df9e --- /dev/null +++ b/python/fastapi-demo.py @@ -0,0 +1,28 @@ +import argparse +import uvicorn +from fastapi import FastAPI + +app = FastAPI() + +# argparse +parser = argparse.ArgumentParser(description='Process some integers.') +parser.add_argument('--port', type=int, default=7074, help='The port number.') +args = parser.parse_args() + +@app.get("/") +async def index(): + return {"message": "Hello World"} + +@app.get("/api/hello") +async def hello(): + return { + "app_name": "FastAPI框架学习", + "app_version": "v0.0.1" + } + +if __name__ == "__main__": + # uvicorn会多创建一个进程,并且stdio独立于控制台,如果(开发时)出现进程没有关闭,可尝试关闭终端 + uvicorn.run(app, host="127.0.0.1", port=args.port) + +# 控制台默认关闭输出信息,如果想要查看控制台输出,请单独启动服务 npm run dev-python +print("python server is running at port:", args.port) \ No newline at end of file diff --git a/python/main.py b/python/main.py new file mode 100644 index 0000000..14e1e84 --- /dev/null +++ b/python/main.py @@ -0,0 +1,57 @@ +from flask import Flask, request, jsonify +from flask_cors import CORS +import argparse +import signal +import sys + +# flask-demo + +# argparse +parser = argparse.ArgumentParser(description='Process some integers.') +parser.add_argument('--port', type=int, default=7074, help='The port number.') +args = parser.parse_args() + +app = Flask(__name__) + +# 配置 CORS,允许所有来源 +CORS(app) + +# 定义路由和处理器 +@app.route('/', methods=['GET']) +def index(): + name = request.args.get('name', 'World') + return jsonify({'message': f'Hello, {name}!'}), 200 + + +@app.route('/api/hello', methods=['GET']) +def hello(): + name = request.args.get('name', 'World') + return jsonify({'message': f'Hello, {name}!'}), 200 + +# 通过信号来退出服务,否则会出现终端显示退出后,实际进程仍在运行 +# 定义信号处理函数 +def signal_handler(sig, frame): + print("[python] [flask] Received signal to terminate the server:", sig) + sys.exit(0) + + # 关闭 Flask 应用 + # func = request.environ.get('werkzeug.server.shutdown') + # if func is None: + # func = lambda: None + # func() + + # 退出主线程 + # threading.main_thread().exit() + +# 注册信号处理函数 +signal.signal(signal.SIGTERM, signal_handler) +signal.signal(signal.SIGINT, signal_handler) + +if __name__ == '__main__': + # 以api方式启动服务会出现警告,请忽略 + app.run(port=args.port) + +# 或许flask内置的stdio与node.js stdio有冲突,导致控制台无法显示信息。 +# 如果想要查看控制台输出,请单独启动服务 npm run dev-python +print("python server is running at port:", args.port) + diff --git a/python/requirements.txt b/python/requirements.txt new file mode 100644 index 0000000..4c606c6 --- /dev/null +++ b/python/requirements.txt @@ -0,0 +1,2 @@ +Flask==3.0.2 +Flask_Cors==4.0.0 diff --git a/python/setup.py b/python/setup.py new file mode 100644 index 0000000..cde3333 --- /dev/null +++ b/python/setup.py @@ -0,0 +1,24 @@ +from cx_Freeze import setup, Executable + +# 创建可执行文件的配置 +executableApp = Executable( + script="main.py", + target_name="pyapp", +) + +# 打包的参数配置 +options = { + "build_exe": { + "build_exe":"./dist/", + "excludes": ["*.txt"], + "optimize": 2, + } +} + +setup( + name="pyapp", + version="1.0", + description="python app", + options=options, + executables=[executableApp] +) \ No newline at end of file