gaoshuaixing 5 anos atrás
commit
066d9249c0

+ 42 - 0
.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 }}

+ 5 - 0
.gitignore

@@ -0,0 +1,5 @@
+node_modules
+out/
+logs/
+run/
+.idea/

+ 14 - 0
.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

+ 201 - 0
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.

+ 1 - 0
LICENSE.md

@@ -0,0 +1 @@
+GNU General Public License

+ 2 - 0
README.md

@@ -0,0 +1,2 @@
+# electron-egg
+A fast, desktop software development framework

+ 49 - 0
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;

+ 6 - 0
app/config/openAuthConfig.js

@@ -0,0 +1,6 @@
+'use strict';
+module.exports = {
+  authInfo: {
+    key: 'Tsld2o4elg',
+  },
+};

+ 0 - 0
app/const/common.js


+ 10 - 0
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;

+ 57 - 0
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;

+ 22 - 0
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;

+ 19 - 0
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;

+ 35 - 0
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 });
+  },
+};

+ 4 - 0
app/lang/en.json

@@ -0,0 +1,4 @@
+{
+  "SUCCESS": "",
+  "SYS_API_ERROR": ""
+}

+ 0 - 0
app/lang/zh.json


+ 8 - 0
app/middleware/auth.js

@@ -0,0 +1,8 @@
+'use strict';
+
+module.exports = () => {
+  return async function auth(ctx, next) {
+
+    await next();
+  };
+};

+ 11 - 0
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);
+};

+ 22 - 0
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;

+ 9 - 0
app/service/base.js

@@ -0,0 +1,9 @@
+'use strict';
+
+const Service = require('egg').Service;
+
+class BaseService extends Service {
+  // base
+}
+
+module.exports = BaseService;

+ 7 - 0
app/service/test.js

@@ -0,0 +1,7 @@
+'use strict';
+
+const BaseService = require('./base');
+
+class TestService extends BaseService {}
+
+module.exports = TestService;

+ 228 - 0
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;
+};

+ 10 - 0
app/view/index.ejs

@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title><%= title %></title>
+    <link rel='stylesheet' href='/stylesheets/style.css' />
+  </head>
+  <body>
+    <h1><%= title %></h1>
+  </body>
+</html>

+ 16 - 0
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

BIN
asset/images/loding.gif


BIN
build/icons/256x256.png


BIN
build/icons/512x512.png


BIN
build/icons/icon.ico


+ 0 - 0
build/script/installer.nsh


+ 6 - 0
config/config.beta.js

@@ -0,0 +1,6 @@
+'use strict';
+// 本地环境-配置文件
+
+exports.logger = {
+  dir: './logs/beta',
+};

+ 97 - 0
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,
+  };
+};

+ 12 - 0
config/config.local.js

@@ -0,0 +1,12 @@
+'use strict';
+// 本地环境-配置文件
+
+/*
+ * 远程调用
+ */
+exports.outApi = {
+  login: 'http://local.com/api/login',
+};
+exports.logger = {
+  dir: './logs/local',
+};

+ 7 - 0
config/config.preview.js

@@ -0,0 +1,7 @@
+'use strict';
+// 本地环境-配置文件
+
+exports.logger = {
+  dir: './logs/preview',
+};
+

+ 13 - 0
config/config.prod.js

@@ -0,0 +1,13 @@
+'use strict';
+// 本地环境-配置文件
+
+/*
+ * 远程调用
+ */
+exports.outApi = {
+  login: 'http://api.local.com/api/login',
+};
+exports.logger = {
+  dir: './logs/prod',
+};
+

+ 22 - 0
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',
+};

+ 22 - 0
index.html

@@ -0,0 +1,22 @@
+<html>
+ <head> 
+  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
+  <style type="text/css">
+    body{
+      margin:0px auto;
+    }
+    #picture1 {
+      position: absolute;
+      left: 50%;
+      top: 50%;
+      transform: translate(-50%, -50%);
+    }
+  </style> 
+  <title></title> 
+ </head> 
+ <body> 
+  <div id="picture1">
+   <img src="./asset/images/loding.gif" />
+  </div>  
+ </body>
+</html>

+ 118 - 0
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()

+ 71 - 0
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);
+}

+ 114 - 0
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"
+  }
+}

+ 12 - 0
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])
+  }
+})

+ 6 - 0
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.

+ 2 - 0
storage/.gitignore

@@ -0,0 +1,2 @@
+*
+!.gitignore