lrf402788946 4 年 前
コミット
fd9bffcf62

+ 29 - 0
.autod.conf.js

@@ -0,0 +1,29 @@
+'use strict';
+
+module.exports = {
+  write: true,
+  prefix: '^',
+  plugin: 'autod-egg',
+  test: [
+    'test',
+    'benchmark',
+  ],
+  dep: [
+    'egg',
+    'egg-scripts',
+  ],
+  devdep: [
+    'egg-ci',
+    'egg-bin',
+    'egg-mock',
+    'autod',
+    'autod-egg',
+    'eslint',
+    'eslint-config-egg',
+  ],
+  exclude: [
+    './test/fixtures',
+    './dist',
+  ],
+};
+

+ 1 - 0
.eslintignore

@@ -0,0 +1 @@
+coverage

+ 3 - 0
.eslintrc

@@ -0,0 +1,3 @@
+{
+  "extends": "eslint-config-egg"
+}

+ 14 - 0
.gitignore

@@ -0,0 +1,14 @@
+logs/
+npm-debug.log
+yarn-error.log
+node_modules/
+package-lock.json
+yarn.lock
+coverage/
+.idea/
+run/
+.DS_Store
+*.sw*
+*.un~
+typings/
+.nyc_output/

+ 12 - 0
.travis.yml

@@ -0,0 +1,12 @@
+
+language: node_js
+node_js:
+  - '10'
+before_install:
+  - npm i npminstall -g
+install:
+  - npminstall
+script:
+  - npm run ci
+after_script:
+  - npminstall codecov && codecov

+ 33 - 0
README.md

@@ -0,0 +1,33 @@
+# service-wechat
+
+
+
+## QuickStart
+
+<!-- add docs here for user -->
+
+see [egg docs][egg] for more detail.
+
+### Development
+
+```bash
+$ npm i
+$ npm run dev
+$ open http://localhost:7001/
+```
+
+### Deploy
+
+```bash
+$ npm start
+$ npm stop
+```
+
+### npm scripts
+
+- Use `npm run lint` to check code style.
+- Use `npm test` to run unit test.
+- Use `npm run autod` to auto detect dependencies upgrade, see [autod](https://www.npmjs.com/package/autod) for more detail.
+
+
+[egg]: https://eggjs.org

+ 12 - 0
app/controller/home.js

@@ -0,0 +1,12 @@
+'use strict';
+
+const Controller = require('egg').Controller;
+
+class HomeController extends Controller {
+  async index() {
+    const { ctx } = this;
+    ctx.body = 'hi, egg';
+  }
+}
+
+module.exports = HomeController;

+ 33 - 0
app/controller/wechat.js

@@ -0,0 +1,33 @@
+'use strict';
+
+const assert = require('assert');
+const _ = require('lodash');
+const uuid = require('uuid');
+const urljoin = require('url-join');
+const Controller = require('egg').Controller;
+
+class WechatController extends Controller {
+
+  // 取得微信AccessToken
+  async accesstoken() {
+    const res = await this.ctx.service.weixin.accesstoken(this.ctx.query);
+    this.ctx.ok({ res });
+  }
+
+  // 取得微信jsapiticket
+  async jsapiticket() {
+    const res = await this.ctx.service.weixin.jsapiticket(this.ctx.query);
+    this.ctx.ok({ res });
+  }
+
+  // 取得微信getsign
+  async getsign() {
+    console.log(this.ctx);
+    const data = await this.ctx.service.weixin.getsign(this.ctx.query);
+    this.ctx.ok({ data });
+  }
+
+
+}
+
+module.exports = WechatController;

+ 14 - 0
app/router.js

@@ -0,0 +1,14 @@
+'use strict';
+
+/**
+ * @param {Egg.Application} app - egg application
+ */
+module.exports = app => {
+  const { router, controller } = app;
+  router.get('/', controller.home.index);
+
+  // 取得acess_token
+  router.get('/api/wechat/accesstoken', controller.wechat.accesstoken);
+  router.get('/api/wechat/jsapiticket', controller.wechat.jsapiticket);
+  router.get('/api/wechat/getsign', controller.wechat.getsign);
+};

+ 145 - 0
app/service/weixin.js

@@ -0,0 +1,145 @@
+'use strict';
+
+const assert = require('assert');
+const uuid = require('uuid');
+const _ = require('lodash');
+const { BusinessError, ErrorCode } = require('naf-core').Error;
+const crypto = require('crypto');
+const Service = require('egg').Service;
+
+class WeixinAuthService extends Service {
+  constructor(ctx) {
+    super(ctx, {});
+  }
+
+  // 取得微信AccessToken
+  async accesstoken({ appid }) {
+    assert(appid, '缺少appid参数项');
+    const key = `visit:auth:accesstoken:${appid}`;
+    const val = await this.app.redis.get(key);
+    const data = {};
+    if (val) {
+      const { access_token } = JSON.parse(val);
+      data.access_token = access_token;
+    } else {
+      const { wxapi } = this.app.config;
+      const appidlist = wxapi;
+      const wxapp = _.find(appidlist, { appid });
+      const feturl = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appid}&secret=${wxapp.appSecret}`;
+      const result = await this.ctx.curl(feturl, {
+        method: 'get',
+        headers: {
+          'content-type': 'application/json',
+        },
+        dataType: 'json',
+      });
+      if (result) {
+        console.log(result);
+        const val = JSON.stringify(result.data);
+        await this.app.redis.set(key, val, 'EX', 7200);
+        data.access_token = result.data.access_token;
+      }
+    }
+    return data;
+  }
+
+  // 取得微信jsapiticket
+  async jsapiticket({ appid }) {
+    assert(appid, '缺少appid参数项');
+    const result = await this.accesstoken({ appid });
+    const accesstoken = result.access_token;
+    console.log(accesstoken);
+    assert(accesstoken, '缺少access_token参数项');
+    const key = `visit:auth:jsapiticket:${appid}`;
+    const val = await this.app.redis.get(key);
+    const data = {};
+    if (val) {
+      const { ticket } = JSON.parse(val);
+      data.ticket = ticket;
+    } else {
+      const feturl = `https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=${accesstoken}&type=jsapi`;
+      const result = await this.ctx.curl(feturl, {
+        method: 'get',
+        headers: {
+          'content-type': 'application/json',
+        },
+        dataType: 'json',
+      });
+      if (result) {
+        console.log(result);
+        if (result.data.errcode === 0) {
+          const val = JSON.stringify(result.data);
+          await this.app.redis.set(key, val, 'EX', 7200);
+          data.ticket = result.data.ticket;
+        }
+
+      }
+    }
+    return data;
+  }
+
+  // 签名算法
+  async getsign({ appid, url }) {
+    assert(appid, '缺少appid参数项');
+    assert(url, '缺少url参数项');
+    const result = await this.jsapiticket({ appid });
+    // const { wxapi } = this.app.config;
+    // const appidlist = wxapi;
+    // const wxapp = _.find(appidlist, { appid });
+    console.log(url);
+    const noncestr = await this.createNonceStr();
+    const timestamp = await this.createTimestamp();
+    const ret = {
+      jsapi_ticket: result.ticket,
+      noncestr,
+      timestamp,
+      url,
+    };
+    console.log(ret);
+    const string = await this.raw(ret);
+    ret.sign = await this.sha1(string);
+    ret.appid = appid;
+    console.log('ret', ret);
+    return ret;
+  }
+
+  // sha1加密
+  async sha1(str) {
+    const shasum = crypto.createHash('sha1');
+    shasum.update(str);
+    str = shasum.digest('hex');
+    return str;
+  }
+
+  // 生成签名的时间戳
+  async createTimestamp() {
+    return parseInt(new Date().getTime() / 1000) + '';
+  }
+
+  // 生成签名的随机串
+  async createNonceStr() {
+    return Math.random().toString(36).substr(2, 15);
+  }
+
+  // 对参数对象进行字典排序
+  // @param  {对象} args 签名所需参数对象
+  // @return {字符串}    排序后生成字符串
+  async raw(args) {
+    let keys = Object.keys(args);
+    keys = keys.sort();
+    const newArgs = {};
+    keys.forEach(function(key) {
+      newArgs[key.toLowerCase()] = args[key];
+    });
+
+    let string = '';
+    for (const k in newArgs) {
+      string += '&' + k + '=' + newArgs[k];
+    }
+    string = string.substr(1);
+    return string;
+  }
+
+}
+
+module.exports = WeixinAuthService;

+ 14 - 0
appveyor.yml

@@ -0,0 +1,14 @@
+environment:
+  matrix:
+    - nodejs_version: '10'
+
+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

+ 81 - 0
config/config.default.js

@@ -0,0 +1,81 @@
+/* eslint valid-jsdoc: "off" */
+
+'use strict';
+
+/**
+ * @param {Egg.EggAppInfo} appInfo app info
+ */
+module.exports = appInfo => {
+  /**
+   * built-in config
+   * @type {Egg.EggAppConfig}
+   **/
+  const config = exports = {};
+
+  // use for cookie sign key, should change to your own and keep security
+  config.keys = appInfo.name + '_1595930154281_6868';
+
+  // add your middleware config here
+  config.middleware = [];
+
+  // add your user config here
+  const userConfig = {
+    // myAppName: 'egg',
+  };
+
+  config.proxy = true;
+  config.hostHeaders = 'x-forwarded-host';
+
+  config.wxapi = [{
+    appid: 'wxdf3ed83c095be97a', // 微信公众号APPID
+    appSecret: '748df7c2a75077a79ae0c971b1638244',
+    baseUrl: 'http://broadcast.waityou24.cn/', // 微信网关地址
+  }];
+
+  config.errorMongo = {
+    details: true,
+  };
+  config.errorHanler = {
+    details: true,
+  };
+
+  // add your config here
+  config.cluster = {
+    listen: {
+      port: 1111,
+    },
+  };
+
+  // redis config
+  config.redis = {
+    client: {
+      port: 6379, // Redis port
+      host: '127.0.0.1', // Redis host
+      password: 123456,
+      db: 0,
+    },
+  };
+
+  config.amqp = {
+    client: {
+      hostname: '127.0.0.1',
+      username: 'visit',
+      password: 'visit',
+      vhost: 'visit',
+    },
+    app: true,
+    agent: true,
+  };
+
+  config.view = {
+    defaultViewEngine: 'nunjucks',
+    mapping: {
+      '.njk': 'nunjucks',
+    },
+  };
+
+  return {
+    ...config,
+    ...userConfig,
+  };
+};

+ 40 - 0
config/config.local.js

@@ -0,0 +1,40 @@
+'use strict';
+
+module.exports = () => {
+  const config = (exports = {});
+
+  config.logger = {
+    level: 'DEBUG',
+    consoleLevel: 'DEBUG',
+  };
+
+  config.wxapi = [{
+    appid: 'wxdf3ed83c095be97a', // 微信公众号APPID
+    appSecret: '748df7c2a75077a79ae0c971b1638244',
+    baseUrl: 'http://broadcast.waityou24.cn/', // 微信网关地址
+  }];
+
+  // mq config
+  config.amqp = {
+    client: {
+      hostname: '127.0.0.1',
+      username: 'wy',
+      password: '1',
+      vhost: 'smart',
+    },
+    app: true,
+    agent: true,
+  };
+
+  // redis config
+  config.redis = {
+    client: {
+      port: 6379, // Redis port
+      host: '127.0.0.1', // Redis host
+      password: 123456,
+      db: 0,
+    },
+  };
+
+  return config;
+};

+ 33 - 0
config/config.prod.js

@@ -0,0 +1,33 @@
+'use strict';
+
+module.exports = () => {
+  const config = exports = {};
+
+  config.logger = {
+    // level: 'DEBUG',
+    // consoleLevel: 'DEBUG',
+  };
+
+  // redis config
+  config.redis = {
+    client: {
+      port: 6379, // Redis port
+      host: '127.0.0.1', // Redis host
+      password: 123456,
+      db: 0,
+    },
+  };
+
+  config.amqp = {
+    client: {
+      hostname: '127.0.0.1',
+      username: 'visit',
+      password: 'visit',
+      vhost: 'visit',
+    },
+    app: true,
+    agent: true,
+  };
+
+  return config;
+};

+ 7 - 0
config/config.secret.js

@@ -0,0 +1,7 @@
+'use strict';
+
+module.exports = {
+  jwt: {
+    secret: 'Ziyouyanfa!@#',
+  },
+};

+ 20 - 0
config/plugin.js

@@ -0,0 +1,20 @@
+'use strict';
+
+exports.multiTenancy = {
+  enable: false,
+};
+
+exports.amqp = {
+  enable: true,
+  package: 'egg-naf-amqp',
+};
+
+exports.redis = {
+  enable: true,
+  package: 'egg-redis',
+};
+
+exports.nunjucks = {
+  enable: true,
+  package: 'egg-view-nunjucks',
+};

+ 17 - 0
ecosystem.config.js

@@ -0,0 +1,17 @@
+'use strict';
+
+const app = 'service-wechat';
+module.exports = {
+  apps: [{
+    name: app, // 应用名称
+    script: './server.js', // 实际启动脚本
+    out: `./logs/${app}.log`,
+    error: `./logs/${app}.err`,
+    watch: [ // 监控变化的目录,一旦变化,自动重启
+      'app', 'config',
+    ],
+    env: {
+      NODE_ENV: 'production', // 环境参数,当前指定为生产环境
+    },
+  }],
+};

+ 5 - 0
jsconfig.json

@@ -0,0 +1,5 @@
+{
+  "include": [
+    "**/*"
+  ]
+}

+ 58 - 0
package.json

@@ -0,0 +1,58 @@
+{
+  "name": "service-wechat",
+  "version": "1.0.0",
+  "description": "",
+  "private": true,
+  "egg": {
+    "framework": "naf-framework-mongoose"
+  },
+  "dependencies": {
+    "crypto": "^1.0.1",
+    "egg": "^2.23.0",
+    "egg-naf-amqp": "0.0.13",
+    "egg-redis": "^2.4.0",
+    "egg-scripts": "^2.11.0",
+    "egg-view-nunjucks": "^2.2.0",
+    "jsonwebtoken": "^8.5.1",
+    "lodash": "^4.17.15",
+    "naf-framework-mongoose": "^0.6.11",
+    "raml2html": "^6.1.0",
+    "silly-datetime": "^0.1.2",
+    "url-join": "^4.0.1",
+    "uuid": "^3.3.3",
+    "xmlreader": "^0.2.3"
+  },
+  "devDependencies": {
+    "autod": "^3.0.1",
+    "autod-egg": "^1.1.0",
+    "egg-bin": "^4.11.0",
+    "egg-ci": "^1.11.0",
+    "egg-mock": "^3.21.0",
+    "eslint": "^5.13.0",
+    "eslint-config-egg": "^7.1.0"
+  },
+  "engines": {
+    "node": ">=10.0.0"
+  },
+  "scripts": {
+    "start": "egg-scripts start --daemon --title=egg-server-service-wechat",
+    "stop": "egg-scripts stop --title=egg-server-service-wechat",
+    "dev": "egg-bin dev",
+    "debug": "egg-bin debug",
+    "test": "npm run lint -- --fix && npm run test-local",
+    "test-local": "egg-bin test",
+    "cov": "egg-bin cov",
+    "lint": "eslint .",
+    "ci": "npm run lint && npm run cov",
+    "autod": "autod"
+  },
+  "ci": {
+    "version": "10"
+  },
+  "repository": {
+    "type": "git",
+    "url": ""
+  },
+  "author": "",
+  "license": "MIT"
+}

+ 9 - 0
server.js

@@ -0,0 +1,9 @@
+
+// eslint-disable-next-line strict
+const egg = require('egg');
+
+const workers = Number(process.argv[2] || require('os').cpus().length);
+egg.startCluster({
+  workers,
+  baseDir: __dirname,
+});

+ 20 - 0
test/app/controller/home.test.js

@@ -0,0 +1,20 @@
+'use strict';
+
+const { app, assert } = require('egg-mock/bootstrap');
+
+describe('test/app/controller/home.test.js', () => {
+  it('should assert', () => {
+    const pkg = require('../../../package.json');
+    assert(app.config.keys.startsWith(pkg.name));
+
+    // const ctx = app.mockContext({});
+    // yield ctx.service.xx();
+  });
+
+  it('should GET /', () => {
+    return app.httpRequest()
+      .get('/')
+      .expect('hi, egg')
+      .expect(200);
+  });
+});