commit 066d9249c0ad292c40197b20c21320081e29fb40 Author: gaoshuaixing Date: Mon Nov 2 16:40:37 2020 +0800 init diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml new file mode 100644 index 0000000..68e01f1 --- /dev/null +++ b/.github/workflows/nodejs.yml @@ -0,0 +1,42 @@ +# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions + +name: Node.js CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + schedule: + - cron: '0 2 * * *' + +jobs: + build: + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + node-version: [8, 10, 12] + os: [ubuntu-latest, windows-latest, macos-latest] + + steps: + - name: Checkout Git Source + uses: actions/checkout@v2 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + + - name: Install Dependencies + run: npm i -g npminstall && npminstall + + - name: Continuous Integration + run: npm run ci + + - name: Code Coverage + uses: codecov/codecov-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..15bd035 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules +out/ +logs/ +run/ +.idea/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..994b4c3 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,14 @@ + +language: node_js +node_js: + - '8' + - '10' + - '12' +before_install: + - npm i npminstall -g +install: + - npminstall +script: + - npm run ci +after_script: + - npminstall codecov && codecov diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..83381e3 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1 @@ +GNU General Public License \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..fa284a0 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# electron-egg +A fast, desktop software development framework diff --git a/app.js b/app.js new file mode 100644 index 0000000..b0e66b0 --- /dev/null +++ b/app.js @@ -0,0 +1,49 @@ +/** + * 全局定义 + * @param app + */ +'use strict'; +global.CODE = require('./app/const/statusCode'); + +class AppBootHook { + constructor(app) { + this.app = app; + global.OS_PLATFORM = process.platform; + global.IS_WIN = /^win/.test(process.platform); + } + + configWillLoad() { + // Ready to call configDidLoad, + // Config, plugin files are referred, + // this is the last chance to modify the config. + } + + configDidLoad() { + // Config, plugin files have been loaded. + } + + async didLoad() { + // All files have loaded, start plugin here. + } + + async willReady() { + // All plugins have started, can do some thing before app ready + } + + async didReady() { + // Worker is ready, can do some things + // don't need to block the app boot. + } + + async serverDidReady() { + // Server is listening. + // const storageFile = './storage'; + // utils.chmodPath(storageFile, '777'); + } + + async beforeClose() { + // Do some thing before app close. + } +} + +module.exports = AppBootHook; diff --git a/app/config/openAuthConfig.js b/app/config/openAuthConfig.js new file mode 100644 index 0000000..a8e0953 --- /dev/null +++ b/app/config/openAuthConfig.js @@ -0,0 +1,6 @@ +'use strict'; +module.exports = { + authInfo: { + key: 'Tsld2o4elg', + }, +}; diff --git a/app/const/common.js b/app/const/common.js new file mode 100644 index 0000000..e69de29 diff --git a/app/const/statusCode.js b/app/const/statusCode.js new file mode 100644 index 0000000..c245bb6 --- /dev/null +++ b/app/const/statusCode.js @@ -0,0 +1,10 @@ +'use strict'; + +let StatusCode; +(function(StatusCode) { + // 系统 + StatusCode[(StatusCode.SUCCESS = 0)] = 'SUCCESS'; + StatusCode[(StatusCode.SYS_API_ERROR = 10001)] = 'SYS_API_ERROR' // api错误 +})(StatusCode || (StatusCode = {})); + +module.exports = StatusCode; diff --git a/app/controller/base.js b/app/controller/base.js new file mode 100644 index 0000000..45bd114 --- /dev/null +++ b/app/controller/base.js @@ -0,0 +1,57 @@ +'use strict'; + +const Controller = require('egg').Controller; + +class BaseController extends Controller { + constructor(ctx) { + super(ctx); + } + + /* + * return success + * @params: object data + * @params: string msg + * @return: object { success, code, msg, data } + */ + sendSuccess(data, msg) { + const { ctx } = this; + ctx.body = { + success: true, + code: 0, + msg, + data, + }; + ctx.status = 200; + } + + /* + * return fail + * @params: object data + * @params: string msg + * @return: object { success, code, msg, data } + */ + sendFail(data, msg, code) { + const { ctx } = this; + ctx.body = { + success: false, + code, + msg, + data, + }; + ctx.status = 200; + } + + /* + * return sendData + * @params: object data + * @params: string msg + * @return: object { success, code, msg, data } + */ + sendData(data) { + const { ctx } = this; + ctx.body = data; + ctx.status = 200; + } +} + +module.exports = BaseController; diff --git a/app/controller/test.js b/app/controller/test.js new file mode 100644 index 0000000..c2f797d --- /dev/null +++ b/app/controller/test.js @@ -0,0 +1,22 @@ +'use strict'; + +const BaseController = require('./base'); + +class TestController extends BaseController { + async index() { + const { app, ctx, service } = this; + const query = ctx.request.query; + console.log('env:%j', app.config.env); + const res = 0; + const data = { + env: app.config.env, + }; + + + + console.log('res:%j', res); + this.sendSuccess(data, 'ok'); + } +} + +module.exports = TestController; diff --git a/app/controller/v1/home.js b/app/controller/v1/home.js new file mode 100644 index 0000000..091219d --- /dev/null +++ b/app/controller/v1/home.js @@ -0,0 +1,19 @@ +'use strict'; + +const BaseController = require('../base'); + +class HomeController extends BaseController { + + async index() { + const { ctx } = this; + + const data = { + title: 'hello electron-egg' + }; + + await ctx.render('index.ejs', data); + } + +} + +module.exports = HomeController; diff --git a/app/extend/context.js b/app/extend/context.js new file mode 100644 index 0000000..812cc18 --- /dev/null +++ b/app/extend/context.js @@ -0,0 +1,35 @@ +/** + * + * @type {{foo(*)}} + */ +'use strict'; +module.exports = { + isMobile() { + const deviceAgent = this.get('user-agent').toLowerCase(); + const agentID = deviceAgent.match(/(iphone|ipod|ipad|android)/); + if (agentID) { + // 手机访问 + return true; + } + // 电脑访问 + return false; + }, + success(msg, data, total) { + this.body = { + success: true, + msg, + result: data, + total, + }; + }, + failure(msg, data) { + this.body = { + success: false, + msg, + result: data, + }; + }, + async infoPage(msg) { + await this.render('500', { msg }); + }, +}; diff --git a/app/lang/en.json b/app/lang/en.json new file mode 100644 index 0000000..a064b6f --- /dev/null +++ b/app/lang/en.json @@ -0,0 +1,4 @@ +{ + "SUCCESS": "", + "SYS_API_ERROR": "" +} \ No newline at end of file diff --git a/app/lang/zh.json b/app/lang/zh.json new file mode 100644 index 0000000..e69de29 diff --git a/app/middleware/auth.js b/app/middleware/auth.js new file mode 100644 index 0000000..5cb81ed --- /dev/null +++ b/app/middleware/auth.js @@ -0,0 +1,8 @@ +'use strict'; + +module.exports = () => { + return async function auth(ctx, next) { + + await next(); + }; +}; diff --git a/app/router/index.js b/app/router/index.js new file mode 100644 index 0000000..c2e2888 --- /dev/null +++ b/app/router/index.js @@ -0,0 +1,11 @@ +'use strict'; + +/** + * @param {Egg.Application} app - egg application + */ +module.exports = app => { + const { router, controller } = app; + router.get('/', controller.v1.home.index); + // html + router.get('/home', controller.v1.home.index); +}; diff --git a/app/schedule/test.js b/app/schedule/test.js new file mode 100644 index 0000000..d673511 --- /dev/null +++ b/app/schedule/test.js @@ -0,0 +1,22 @@ +'use strict'; + +const Subscription = require('egg').Subscription; + +/** + * test + */ + +class Test extends Subscription { + static get schedule() { + return { + interval: '360m', + type: 'worker', + immediate: false, + disable: true, + }; + } + + async subscribe() {} +} + +module.exports = Test; diff --git a/app/service/base.js b/app/service/base.js new file mode 100644 index 0000000..2e0ba46 --- /dev/null +++ b/app/service/base.js @@ -0,0 +1,9 @@ +'use strict'; + +const Service = require('egg').Service; + +class BaseService extends Service { + // base +} + +module.exports = BaseService; diff --git a/app/service/test.js b/app/service/test.js new file mode 100644 index 0000000..47d90b2 --- /dev/null +++ b/app/service/test.js @@ -0,0 +1,7 @@ +'use strict'; + +const BaseService = require('./base'); + +class TestService extends BaseService {} + +module.exports = TestService; diff --git a/app/utils/index.js b/app/utils/index.js new file mode 100644 index 0000000..e6aa964 --- /dev/null +++ b/app/utils/index.js @@ -0,0 +1,228 @@ +'use strict'; + +// MD5加密工具 +const crypto = require('crypto'); +// 使用crypto进行MD5加密 +const md5 = crypto.createHash('md5'); + +/* 通用函数集合 */ +// UTC时间格式化成本地时间 +exports.formatUTC = UTCDateString => { + if (!UTCDateString) { + return '-'; + } + // 格式化显示 + function formatFunc(str) { + return str > 9 ? str : '0' + str; + } + // 这步是关键 + const date2 = new Date(UTCDateString); + const year = date2.getFullYear(); + const mon = formatFunc(date2.getMonth() + 1); + const day = formatFunc(date2.getDate()); + const hour = date2.getHours(); + const min = date2.getMinutes(); + const second = date2.getSeconds(); + + const dateStr = + year + '-' + mon + '-' + day + ' ' + hour + ':' + min + ':' + second; + return dateStr; +}; +// 生成8位数的uid +exports.createNewUid = () => { + /* 生成8位随机整数,不能有4位连续数字,不能有4位重复数字 */ + // 4位重复数字 + // let reg1 = /\d{4}/; + const reg1 = /([\d])\1{3}/; + // 4位连续数字 + const reg2 = /(0(?=1)|1(?=2)|2(?=3)|3(?=4)|4(?=5)|5(?=6)|6(?=7)|7(?=8)|8(?=9)|9(?=0)){3}|(?:0(?=9)|9(?=8)|8(?=7)|7(?=6)|6(?=5)|5(?=4)|4(?=3)|3(?=2)|2(?=1)|1(?=0)){3}/; + // const numStr1 = '10122229'; + // const numStr2 = '78902100'; + // console.log(`reg1 is ${reg1.test(numStr1)}`); + // console.log(`reg2 is ${reg2.test(numStr2)}`); + + // 生成uid(随机20次应该能有1次满足条件) + let uid = null; + for (let i = 0; i < 20; i++) { + // 生成随机整数(范围11111111到19878711) + uid = String(parseInt(Math.random() * 8767600) + 11111111); + // console.log(`uid is ${uid}`) + // const uid = "89016530"; + // console.log(`reg1 is ${reg1.test(uid)}`); + // console.log(`reg2 is ${reg2.test(uid)}`); + if (!reg1.test(uid) && !reg2.test(uid)) { + // console.log(`uid is 1111`) + return uid; + } + } +}; + +// 生成密码加密的盐slat(8位字符串) +exports.saltPwd = () => { + let str = ''; + const arr = [ + '0', + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + 'a', + 'b', + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i', + 'j', + 'k', + 'l', + 'm', + 'n', + 'o', + 'p', + 'q', + 'r', + 's', + 't', + 'u', + 'v', + 'w', + 'x', + 'y', + 'z', + ]; + + let pos = null; + for (let i = 0; i < 8; i++) { + pos = Math.round(Math.random() * (arr.length - 1)); + str += arr[pos]; + } + return str; +}; +// 生成随机字符串(数字(0-9),字母(a-z,A-Z)) +exports.randomWord = (randomFlag, min, max) => { + let str = ''; + let range = min; + const arr = [ + '0', + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + 'a', + 'b', + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i', + 'j', + 'k', + 'l', + 'm', + 'n', + 'o', + 'p', + 'q', + 'r', + 's', + 't', + 'u', + 'v', + 'w', + 'x', + 'y', + 'z', + 'A', + 'B', + 'C', + 'D', + 'E', + 'F', + 'G', + 'H', + 'I', + 'J', + 'K', + 'L', + 'M', + 'N', + 'O', + 'P', + 'Q', + 'R', + 'S', + 'T', + 'U', + 'V', + 'W', + 'X', + 'Y', + 'Z', + ]; + + // 随机产生 + if (randomFlag) { + range = Math.round(Math.random() * (max - min)) + min; + } + let pos = null; + for (let i = 0; i < range; i++) { + pos = Math.round(Math.random() * (arr.length - 1)); + str += arr[pos]; + } + return str; + // 使用方法 + // 生成3-32位随机串:randomWord(true, 3, 32) + // 生成43位随机串:randomWord(false, 43) +}; +// 生成两数之间随机数 +exports.randomNums = (min, max) => { + return Math.round(Math.random() * (max - min) + min); +}; +// 客户端密码加密 +exports.saltPwdMd5 = (password, saltPwd) => { + const md5 = crypto.createHash('md5'); + // 加了盐的客户端密码 + const saltPassword = password + ':' + saltPwd; + // 加盐的密码再用MD5加密 + const saltPasswordMd5 = md5.update(saltPassword).digest('hex'); + return saltPasswordMd5; +}; +exports.createToken = data => { + // console.log(`data is ${JSON.stringify(data)}`); + const { uid, app } = data; + // 当前时间戳 + const created = Math.floor(Date.now() / 1000); + + const token = app.jwt.sign({ uid, created }, app.config.jwt.secret, { + expiresIn: '30d', + }); + + return token; +}; +exports.createAdminToken = data => { + // console.log(`data is ${JSON.stringify(data)}`); + const { id, role, app } = data; + // 当前时间戳 + const created = Math.floor(Date.now() / 1000); + + const token = app.jwt.sign({ id, role, created }, app.config.jwt.secret, { + expiresIn: '30d', + }); + + return token; +}; diff --git a/app/view/index.ejs b/app/view/index.ejs new file mode 100644 index 0000000..d295a27 --- /dev/null +++ b/app/view/index.ejs @@ -0,0 +1,10 @@ + + + + <%= title %> + + + +

<%= title %>

+ + \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..aea9477 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,16 @@ +environment: + matrix: + - nodejs_version: '8' + - nodejs_version: '10' + - nodejs_version: '12' + +install: + - ps: Install-Product node $env:nodejs_version + - npm i npminstall && node_modules\.bin\npminstall + +test_script: + - node --version + - npm --version + - npm run test + +build: off diff --git a/asset/images/loding.gif b/asset/images/loding.gif new file mode 100644 index 0000000..733d31d Binary files /dev/null and b/asset/images/loding.gif differ diff --git a/build/icons/256x256.png b/build/icons/256x256.png new file mode 100644 index 0000000..f2c3df7 Binary files /dev/null and b/build/icons/256x256.png differ diff --git a/build/icons/512x512.png b/build/icons/512x512.png new file mode 100644 index 0000000..42188fe Binary files /dev/null and b/build/icons/512x512.png differ diff --git a/build/icons/icon.ico b/build/icons/icon.ico new file mode 100644 index 0000000..fb97598 Binary files /dev/null and b/build/icons/icon.ico differ diff --git a/build/script/installer.nsh b/build/script/installer.nsh new file mode 100644 index 0000000..e69de29 diff --git a/config/config.beta.js b/config/config.beta.js new file mode 100644 index 0000000..d52faa0 --- /dev/null +++ b/config/config.beta.js @@ -0,0 +1,6 @@ +'use strict'; +// 本地环境-配置文件 + +exports.logger = { + dir: './logs/beta', +}; diff --git a/config/config.default.js b/config/config.default.js new file mode 100644 index 0000000..8c12274 --- /dev/null +++ b/config/config.default.js @@ -0,0 +1,97 @@ +/* eslint valid-jsdoc: "off" */ + +'use strict'; +const path = require('path'); +/** + * @param {Egg.EggAppInfo} appInfo app info + */ +module.exports = appInfo => { + /** + * built-in config + * @type {Egg.EggAppConfig} + **/ + const config = {}; + + // use for cookie sign key, should change to your own and keep security + config.keys = appInfo.name + '_1552879336505_7432'; + + // add your middleware config here + config.middleware = []; + + // add your user config here + const userConfig = { + // myAppName: 'egg', + }; + + config.cluster = { + listen: { + port: 7068, + hostname: '0.0.0.0', + // path: '/var/run/egg.sock', + }, + }; + + // jwt插件配置(盐) + config.jwt = { + secret: 'jcgame88', + }; + + /* 跨域插件配置-start */ + config.security = { + xframe: { + enable: false, + }, + ignore: '/api', + // 跨域允许的域名列表-配置 + domainWhiteList: [ + '*', + // 'http://127.0.0.1:8080' + ], + methodnoallow: { enable: false }, + // 安全配置(很重要) + csrf: { + enable: false, + ignoreJSON: true, // 默认为 false,当设置为 true 时,将会放过所有 content-type 为 `application/json` 的请求 + }, + }; + // 允许的跨域请求类型(GET,POST) + config.cors = { + origin: '*', + // allowMethods: 'GET,POST', + allowMethods: 'GET,POST,HEAD,PUT,OPTIONS,DELETE,PATCH', + }; + /* 跨域插件配置-end */ + + // 校验插件配置(支持 parameter的所有配置项) + config.validate = { + // convert: false, + // validateRoot: false, + }; + + // 获取真实ip + config.maxProxyCount = 2; + + config.view = { + root: [path.join(appInfo.baseDir, 'app/view')].join(','), + mapping: { + '.ejs': 'ejs', + }, + }; + + config.static = { + // 静态化访问前缀,如:`http://127.0.0.1:7001/static/images/logo.png` + prefix: '/', + dir: [path.join(appInfo.baseDir, 'app/public')], // `String` or `Array:[dir1, dir2, ...]` 静态化目录,可以设置多个静态化目录 + dynamic: true, // 如果当前访问的静态资源没有缓存,则缓存静态文件,和`preload`配合使用; + preload: false, + maxAge: 31536000, // in prod env, 0 in other envs + buffer: true, // in prod env, false in other envs + }; + + config.ejs = {}; + + return { + ...config, + ...userConfig, + }; +}; diff --git a/config/config.local.js b/config/config.local.js new file mode 100644 index 0000000..c7d8a45 --- /dev/null +++ b/config/config.local.js @@ -0,0 +1,12 @@ +'use strict'; +// 本地环境-配置文件 + +/* + * 远程调用 + */ +exports.outApi = { + login: 'http://local.com/api/login', +}; +exports.logger = { + dir: './logs/local', +}; diff --git a/config/config.preview.js b/config/config.preview.js new file mode 100644 index 0000000..fd8eb69 --- /dev/null +++ b/config/config.preview.js @@ -0,0 +1,7 @@ +'use strict'; +// 本地环境-配置文件 + +exports.logger = { + dir: './logs/preview', +}; + diff --git a/config/config.prod.js b/config/config.prod.js new file mode 100644 index 0000000..b5dd13d --- /dev/null +++ b/config/config.prod.js @@ -0,0 +1,13 @@ +'use strict'; +// 本地环境-配置文件 + +/* + * 远程调用 + */ +exports.outApi = { + login: 'http://api.local.com/api/login', +}; +exports.logger = { + dir: './logs/prod', +}; + diff --git a/config/plugin.js b/config/plugin.js new file mode 100644 index 0000000..96047be --- /dev/null +++ b/config/plugin.js @@ -0,0 +1,22 @@ +'use strict'; + +/* + *Egg插件 + */ + +// jwt登录状态验证插件 +exports.jwt = { + enable: true, + package: 'egg-jwt', +}; + +// 跨域插件 +exports.cors = { + enable: true, + package: 'egg-cors', +}; + +exports.ejs = { + enable: true, + package: 'egg-view-ejs', +}; diff --git a/index.html b/index.html new file mode 100644 index 0000000..a46e498 --- /dev/null +++ b/index.html @@ -0,0 +1,22 @@ + + + + + + + +
+ +
+ + \ No newline at end of file diff --git a/main.js b/main.js new file mode 100644 index 0000000..3e5ba08 --- /dev/null +++ b/main.js @@ -0,0 +1,118 @@ +const {app, BrowserWindow, Menu, shell} = require('electron') +const path = require('path') +const glob = require('glob') +const getPort = require('get-port') +const eggLauncher = require('./main/lanucher') + +// glogger +global.GLOGGER = require('electron-log') +GLOGGER.transports.console.level = 'silly' +GLOGGER.transports.file.file = './logs/main.log' + +// 主窗口 +global.MAIN_WINDOW = null + +// console.log('path:', app.getAppPath()) +// return; +let options = { + env: 'prod', + eggPort: 7068, + workers: 1 +}; +for (let i = 0; i < process.argv.length; i++) { + const tmpArgv = process.argv[i]; + if (tmpArgv.indexOf('--env=') !== -1) { + options.env = tmpArgv.substr(6); + } +} +GLOGGER.info('options', options); + +if (process.mas) app.setName('box') + +app.on('web-contents-created', (e, webContents) => { + webContents.on('new-window', (event, url) => { + event.preventDefault(); + shell.openExternal(url); + }); +}); + +async function createWindow () { + MAIN_WINDOW = new BrowserWindow({ + width: 800, + height: 800, + minWidth: 800, + minHeight: 600, + webPreferences: { + //webSecurity: false, + nodeIntegration: true, + preload: path.join(__dirname, 'preload.js') + }, + //frame: false, + //titleBarStyle: 'hidden' + }) + + // if (process.platform === 'linux') { + // windowOptions.icon = path.join(__dirname, '/assets/app-icon/png/512.png') + // } + + if (options.env === 'prod') { + //隐藏菜单 + Menu.setApplicationMenu(null) + } + + // loding页 + MAIN_WINDOW.loadURL(path.join('file://', __dirname, '/index.html')) + + // egg服务 + setTimeout(function(){ + startServer(options) + }, 100) + + return MAIN_WINDOW; +} + +async function startServer (options) { + let startRes = null; + options.eggPort = await getPort({port: options.eggPort}) + let params = { + port: options.eggPort, + title: 'electron-egg', + workers: 1, + env: options.env + } + startRes = await eggLauncher.start(params).then((res) => res, (err) => err) + GLOGGER.info('startRes:', startRes); + if (startRes === 'success') { + let url = 'http://localhost:' + options.eggPort + MAIN_WINDOW.loadURL(url) + + return + } + app.relaunch() +} + +async function initialize () { + loadDemos() + app.whenReady().then(() => { + createWindow() + app.on('activate', function () { + if (BrowserWindow.getAllWindows().length === 0) { + createWindow() + } + }) + }) + + app.on('window-all-closed', function () { + if (process.platform !== 'darwin') { + console.log('window-all-closed quit') + app.quit() + } + }) +} + +function loadDemos () { + let files = glob.sync(path.join(__dirname, 'main/**/*.js')) + files.forEach((file) => { require(file) }) +} + +initialize() \ No newline at end of file diff --git a/main/lanucher.js b/main/lanucher.js new file mode 100644 index 0000000..b4d1111 --- /dev/null +++ b/main/lanucher.js @@ -0,0 +1,71 @@ +'use strict'; + +const path = require('path'); +const startCluster = require('egg-cluster').startCluster; +const {app} = require('electron'); + +exports = module.exports; + +exports.start = function (argv) { + const { env } = process; + + let baseDir = app.getAppPath(); + argv.baseDir = baseDir; + argv.framework = path.join(baseDir, 'node_modules/egg'); + + const pkgInfo = require(path.join(baseDir, 'package.json')); + argv.title = argv.title || `egg-server-${pkgInfo.name}`; + + // normalize env + env.HOME = baseDir; + env.NODE_ENV = 'production'; + + // it makes env big but more robust + env.PATH = env.Path = [ + // for nodeinstall + path.join(baseDir, 'node_modules/.bin'), + // support `.node/bin`, due to npm5 will remove `node_modules/.bin` + path.join(baseDir, '.node/bin'), + // adjust env for win + env.PATH || env.Path, + ].filter(x => !!x).join(path.delimiter); + + // for alinode + env.ENABLE_NODE_LOG = 'YES'; + env.NODE_LOG_DIR = env.NODE_LOG_DIR || path.join(baseDir, 'logs/alinode'); + + // cli argv -> process.env.EGG_SERVER_ENV -> `undefined` then egg will use `prod` + if (argv.env) { + // if undefined, should not pass key due to `spwan`, https://github.com/nodejs/node/blob/master/lib/child_process.js#L470 + env.EGG_SERVER_ENV = argv.env; + } + + // remove unused properties from stringify, alias had been remove by `removeAlias` + const ignoreKeys = [ '_', '$0', 'env', 'daemon', 'stdout', 'stderr', 'timeout', 'ignore-stderr', 'node' ]; + const clusterOptions = stringify(argv, ignoreKeys); + const options = JSON.parse(clusterOptions); + // console.log('options:', { + // argv, + // options + // }); + + return new Promise((resolve, reject) => { + startCluster(options, function(){ + resolve('success'); + }); + }); +}; + +exports.stop = function () { + return true; +}; + +function stringify(obj, ignore) { + const result = {}; + Object.keys(obj).forEach(key => { + if (!ignore.includes(key)) { + result[key] = obj[key]; + } + }); + return JSON.stringify(result); +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..627fdfe --- /dev/null +++ b/package.json @@ -0,0 +1,114 @@ +{ + "name": "electron-egg", + "version": "1.0.0", + "description": "A fast, desktop software development framework", + "main": "main.js", + "scripts": { + "start": "electron .", + "dev": "electron . --env=local", + "build-w": "electron-builder -w", + "build-m": "electron-builder -m", + "build-l": "electron-builder -l" + }, + "build": { + "productName": "electron-egg", + "appId": "com.electron.egg", + "copyright": "wallace5303", + "directories": { + "output": "out" + }, + "asar": true, + "files": [ + "**/*" + ], + "electronDownload": { + "mirror": "https://npm.taobao.org/mirrors/electron/" + }, + "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": "demo" + }, + "publish": [ + { + "provider": "generic", + "url": "https://github.com/wallace5303/electron-egg" + } + ], + "dmg": { + "contents": [ + { + "x": 410, + "y": 150, + "type": "link", + "path": "/Applications" + }, + { + "x": 130, + "y": 150, + "type": "file" + } + ] + }, + "mac": { + "icon": "build/icons/icon.icns" + }, + "win": { + "icon": "build/icons/icon.ico", + "artifactName": "${productName}_windows_${version}.${ext}", + "target": [ + { + "target": "nsis", + "arch": [ + "ia32" + ] + } + ] + }, + "linux": { + "icon": "build/icons" + } + }, + "repository": "https://github.com/wallace5303/electron-egg.git", + "keywords": [ + "Electron", + "Egg" + ], + "author": "wallace5303", + "license": "Apache", + "devDependencies": { + "devtron": "^1.4.0", + "electron": "^8.4.1", + "electron-builder": "^22.7.0", + "autod": "^3.0.1", + "autod-egg": "^1.1.0", + "egg-bin": "^4.12.3", + "egg-ci": "^1.11.0", + "egg-mock": "^3.21.0", + "eslint": "^5.13.0", + "eslint-config-egg": "^7.1.0", + "eslint-plugin-prettier": "^3.0.1", + "prettier": "^1.16.4", + "webstorm-disable-index": "^1.2.0" + }, + "dependencies": { + "egg": "^2.15.1", + "egg-cors": "^2.2.0", + "egg-jwt": "^3.1.6", + "egg-view-ejs": "^2.0.0", + "electron-log": "^4.2.2", + "electron-store": "^6.0.1", + "electron-updater": "^4.3.5", + "get-port": "^5.1.1", + "glob": "^7.1.6", + "lodash": "^4.17.11", + "keyv": "^3.1.0", + "semver": "^5.4.1" + } +} diff --git a/preload.js b/preload.js new file mode 100644 index 0000000..c2ff9c1 --- /dev/null +++ b/preload.js @@ -0,0 +1,12 @@ +// All of the Node.js APIs are available in the preload process. +// It has the same sandbox as a Chrome extension. +window.addEventListener('DOMContentLoaded', () => { + const replaceText = (selector, text) => { + const element = document.getElementById(selector) + if (element) element.innerText = text + } + + for (const type of ['chrome', 'node', 'electron']) { + replaceText(`${type}-version`, process.versions[type]) + } +}) diff --git a/renderer.js b/renderer.js new file mode 100644 index 0000000..d3bdade --- /dev/null +++ b/renderer.js @@ -0,0 +1,6 @@ +// This file is required by the index.html file and will +// be executed in the renderer process for that window. +// No Node.js APIs are available in this process because +// `nodeIntegration` is turned off. Use `preload.js` to +// selectively enable features needed in the rendering +// process. diff --git a/storage/.gitignore b/storage/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/storage/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file