lrf402788946 3 年之前
当前提交
f8a852fed2
共有 43 个文件被更改,包括 1240 次插入0 次删除
  1. 29 0
      .autod.conf.js
  2. 1 0
      .eslintignore
  3. 3 0
      .eslintrc
  4. 42 0
      .github/workflows/nodejs.yml
  5. 14 0
      .gitignore
  6. 12 0
      .travis.yml
  7. 14 0
      README.md
  8. 34 0
      app/controller/chat.js
  9. 17 0
      app/controller/home.js
  10. 61 0
      app/controller/system/.admin.js
  11. 61 0
      app/controller/system/.menu.js
  12. 60 0
      app/controller/system/.tenant.js
  13. 13 0
      app/controller/system/admin.js
  14. 20 0
      app/controller/system/menu.js
  15. 13 0
      app/controller/system/tenant.js
  16. 22 0
      app/controller/system/weixin.js
  17. 35 0
      app/middleware/order-num.js
  18. 18 0
      app/middleware/tenant-check.js
  19. 16 0
      app/middleware/tenant-use.js
  20. 27 0
      app/model/member/member.js
  21. 24 0
      app/model/member/points_record.js
  22. 26 0
      app/model/member/save_record.js
  23. 30 0
      app/model/system/admin.js
  24. 25 0
      app/model/system/menu.js
  25. 26 0
      app/model/system/tenant.js
  26. 16 0
      app/router.js
  27. 33 0
      app/service/goods/index.js
  28. 83 0
      app/service/install.js
  29. 15 0
      app/service/member/member.js
  30. 39 0
      app/service/system/admin.js
  31. 80 0
      app/service/system/menu.js
  32. 25 0
      app/service/system/tenant.js
  33. 73 0
      app/service/system/weixin.js
  34. 12 0
      app/z_router/system/admin.js
  35. 12 0
      app/z_router/system/menu.js
  36. 13 0
      app/z_router/system/tenant.js
  37. 11 0
      app/z_router/system/weixin.js
  38. 14 0
      appveyor.yml
  39. 75 0
      config/config.default.js
  40. 7 0
      config/config.secret.js
  41. 15 0
      config/plugin.js
  42. 54 0
      package.json
  43. 20 0
      test/app/controller/home.test.js

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

+ 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: [10]
+        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 }}

+ 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

+ 14 - 0
README.md

@@ -0,0 +1,14 @@
+顾客用户上设置积分功能,能充值,充值达到阶段享会员全场折扣,有客服界面可以直接联系,退单功能,邀请人数,邀请人可得被邀请人购买后赠积分功能,积分换购。后台上有各种记录,如成本价单品计算汇总计算,销售汇总,利润率,配送费汇总,新客量,客单价,折扣特价商品等。
+
+线上营业时间也要能调整 准备先做夜里配送 老板怕影响白班销售 所以白天暂时先不考虑做
+* 配送分为2中:现时配送/24小时内配送 => 主要是应对店里没有库存的货物
+
+## 项目代码相关
+
+### 商品
+  就那玩应,还不知道有啥呢,不过肯定得有库存这个问题就是了
+
+### 会员相关
+  1. 办会员=>填写基础信息即可成为会员 ,进入会员表
+  2. 消费/获取积分 => 进入积分表,记录会员什么时间,什么操作, 获取/消耗的积分
+  3. 可以充值储蓄(给福利,多给点之类的) ,进入save_record表,记录充值相关信息

+ 34 - 0
app/controller/chat.js

@@ -0,0 +1,34 @@
+'use strict';
+const Controller = require('egg').Controller;
+const { CrudController } = require('naf-framework-mongoose-free/lib/controller');
+
+//
+class ChatController extends Controller {
+  constructor(ctx) {
+    super(ctx);
+    this.mq = this.ctx.mq;
+    this.redis = this.app.redis;
+  }
+  async online() {
+    const query = this.ctx.query;
+    const { person } = query;
+    console.log(person);
+    if (this.mq) {
+      console.log('mq ojbk!');
+    }
+    if (this.redis) {
+      console.log('redis ojbk!');
+      const p = await this.redis.get(person);
+      // if(p) await this.redis.set(person,'')
+
+    }
+    this.ctx.ok();
+  }
+
+  async send() {
+    const body = this.ctx.request.body;
+    console.log(body);
+    this.ctx.ok();
+  }
+}
+module.exports = CrudController(ChatController, {});

+ 17 - 0
app/controller/home.js

@@ -0,0 +1,17 @@
+'use strict';
+
+const Controller = require('egg').Controller;
+
+class HomeController extends Controller {
+  async index() {
+    const { ctx } = this;
+    ctx.body = 'hi, egg';
+  }
+  // 项目初始化
+  async install() {
+    await this.ctx.service.install.index(this.ctx.request.body);
+    this.ctx.body = 'ok';
+  }
+}
+
+module.exports = HomeController;

+ 61 - 0
app/controller/system/.admin.js

@@ -0,0 +1,61 @@
+module.exports = {
+  create: {
+    requestBody: [
+      "name",
+      "login_id",
+      "password",
+      "menu",
+      "is_root",
+      "openid",
+      "remark",
+    ],
+  },
+  destroy: {
+    params: ["!id"],
+    service: "delete",
+  },
+  update: {
+    params: ["!id"],
+    requestBody: [
+      "name",
+      "login_id",
+      "password",
+      "menu",
+      "is_root",
+      "openid",
+      "status",
+      "remark",
+    ],
+  },
+  show: {
+    parameters: {
+      params: ["!id"],
+    },
+    service: "fetch",
+  },
+  index: {
+    parameters: {
+      query: {
+        name: "%name%",
+        login_id: "login_id",
+        openid: "openid",
+        is_root: "is_root",
+        status: "status",
+      },
+      // options: {
+      //   "meta.state": 0 // 默认条件
+      // },
+    },
+    service: "query",
+    options: {
+      query: ["skip", "limit"],
+      sort: ["meta.createdAt"],
+      desc: true,
+      count: true,
+    },
+  },
+  login: {
+    requestBody: ["login_id", "password"],
+    service: "login",
+  },
+};

+ 61 - 0
app/controller/system/.menu.js

@@ -0,0 +1,61 @@
+module.exports = {
+  create: {
+    requestBody: [
+      "type",
+      "title",
+      "route",
+      "pid",
+      "disabled",
+      "sort",
+      "params",
+      "remark",
+      "icon",
+    ],
+  },
+  destroy: {
+    params: ["!id"],
+    service: "delete",
+  },
+  update: {
+    params: ["!id"],
+    requestBody: [
+      "type",
+      "title",
+      "route",
+      "pid",
+      "disabled",
+      "sort",
+      "params",
+      "remark",
+      "icon",
+    ],
+  },
+  show: {
+    parameters: {
+      params: ["!id"],
+    },
+    service: "fetch",
+  },
+  index: {
+    parameters: {
+      query: {
+        type: "type",
+        title: "title",
+        route: "route",
+        pid: "pid",
+        disabled: "disabled",
+      },
+    },
+    service: "query",
+    options: {
+      query: ["skip", "limit"],
+      sort: ["sort"],
+      desc: true,
+      count: true,
+    },
+  },
+  getUserMenu: {
+    params: ["!id"],
+    service: "getUserMenu",
+  },
+};

+ 60 - 0
app/controller/system/.tenant.js

@@ -0,0 +1,60 @@
+module.exports = {
+  create: {
+    requestBody: [
+      "_tenant",
+      "name",
+      "is_use",
+      "remark",
+      "params",
+      "img",
+      "menus",
+    ],
+  },
+  destroy: {
+    params: ["!id"],
+    service: "delete",
+  },
+  update: {
+    params: ["!id"],
+    requestBody: [
+      "_tenant",
+      "name",
+      "is_use",
+      "remark",
+      "params",
+      "img",
+      "menus",
+    ],
+  },
+  show: {
+    parameters: {
+      params: ["!id"],
+    },
+    service: "fetch",
+  },
+  index: {
+    parameters: {
+      query: {
+        _tenant: "_tenant",
+        name: "name",
+        is_use: "is_use",
+      },
+      // options: {
+      //   "meta.state": 0 // 默认条件
+      // },
+    },
+    service: "query",
+    options: {
+      query: ["skip", "limit"],
+      sort: ["meta.createdAt"],
+      desc: true,
+      count: true,
+    },
+  },
+  getTenant: {
+    parameters: {
+      params: ["!_tenant"],
+    },
+    service: "getTenant",
+  },
+};

+ 13 - 0
app/controller/system/admin.js

@@ -0,0 +1,13 @@
+'use strict';
+const meta = require('./.admin.js');
+const Controller = require('egg').Controller;
+const { CrudController } = require('naf-framework-mongoose-free/lib/controller');
+
+// 管理员
+class AdminController extends Controller {
+  constructor(ctx) {
+    super(ctx);
+    this.service = this.ctx.service.system.admin;
+  }
+}
+module.exports = CrudController(AdminController, meta);

+ 20 - 0
app/controller/system/menu.js

@@ -0,0 +1,20 @@
+'use strict';
+
+const _ = require('lodash');
+const meta = require('./.menu.js');
+const Controller = require('egg').Controller;
+const { CrudController } = require('naf-framework-mongoose-free/lib/controller');
+
+// 教师申请讲课表管理
+class MenuController extends Controller {
+  constructor(ctx) {
+    super(ctx);
+    this.service = this.ctx.service.system.menu;
+  }
+  async index() {
+    const data = await this.service.findProject();
+    this.ctx.ok({ data });
+  }
+}
+
+module.exports = CrudController(MenuController, meta);

+ 13 - 0
app/controller/system/tenant.js

@@ -0,0 +1,13 @@
+'use strict';
+const meta = require('./.tenant.js');
+const Controller = require('egg').Controller;
+const { CrudController } = require('naf-framework-mongoose-free/lib/controller');
+
+// 站点
+class TenantController extends Controller {
+  constructor(ctx) {
+    super(ctx);
+    this.service = this.ctx.service.system.tenant;
+  }
+}
+module.exports = CrudController(TenantController, meta);

+ 22 - 0
app/controller/system/weixin.js

@@ -0,0 +1,22 @@
+'use strict';
+const Controller = require('egg').Controller;
+const { CrudController } = require('naf-framework-mongoose-free/lib/controller');
+
+// 微信
+class WeixinController extends Controller {
+  constructor(ctx) {
+    super(ctx);
+    this.service = this.ctx.service.system.weixin;
+  }
+
+  async appAuth() {
+    const data = await this.service.appAuth(this.ctx.query, this.ctx.tenant);
+    this.ctx.ok({ data });
+  }
+
+  async decrypt() {
+    const data = await this.service.getSecret(this.ctx.request.body);
+    this.ctx.ok({ data });
+  }
+}
+module.exports = CrudController(WeixinController, {});

+ 35 - 0
app/middleware/order-num.js

@@ -0,0 +1,35 @@
+'use strict';
+const _ = require('lodash');
+const getOrderNum = async (ctx, data) => {
+  const keys = [ 'breakfast', 'lunch', 'dinner' ];
+  for (const key of keys) {
+    if (_.get(data.arrange, key) && _.isArray(_.get(data.arrange, key))) {
+      for (const i of _.get(data.arrange, key)) {
+        const res = await ctx.model.Dining.Menu.findById(i._id);
+        console.log(res.order);
+        if (res) i.order = res.order;
+      }
+    }
+  }
+  return data;
+};
+module.exports = options => {
+  return async function orderNum(ctx, next) {
+    await next();
+    const request = ctx.request;
+    const { method } = request;
+    if (method === 'GET') {
+      let { data } = ctx.body;
+      data = JSON.parse(JSON.stringify(data));
+      // 将所有的菜谱中的点餐数量都换成原id的数量,做成实时数据
+      if (_.isArray(data)) {
+        for (let d of data) {
+          d = await getOrderNum(ctx, d);
+        }
+      } else if (data) {
+        data = await getOrderNum(ctx, data);
+      }
+      ctx.body.data = data;
+    }
+  };
+};

+ 18 - 0
app/middleware/tenant-check.js

@@ -0,0 +1,18 @@
+'use strict';
+const _ = require('lodash');
+const { BusinessError, ErrorCode } = require('naf-core').Error;
+module.exports = options => {
+  return async function tenantCheck(ctx, next) {
+    const request = ctx.request;
+    if (request.method !== 'GET') {
+      const tenant = _.get(request, 'header.x-tenant');
+      console.log(request.body);
+      // 该中间只能通过master/与内容中的_tenant字段相同 的权限进行增删改
+      if (tenant !== 'master') {
+        const _tenant = _.get(request.body, '_tenant');
+        if (!_tenant || _tenant !== tenant) { throw new BusinessError(ErrorCode.ACCESS_DENIED, '您没有访问的权限!'); }
+      }
+    }
+    await next();
+  };
+};

+ 16 - 0
app/middleware/tenant-use.js

@@ -0,0 +1,16 @@
+'use strict';
+const _ = require('lodash');
+const { BusinessError, ErrorCode } = require('naf-core').Error;
+const tenantIsUse = async ctx => {
+  const _tenant = ctx.tenant;
+  const res = await ctx.model.System.Tenant.findOne({ _tenant });
+  if (!res) throw new BusinessError(ErrorCode.SERVICE_FAULT, '该分站未开通!');
+  if (!res.is_use) throw new BusinessError(ErrorCode.SERVICE_FAULT, '该分站处于关闭状态!');
+  return true;
+};
+module.exports = options => {
+  return async function tenantUse(ctx, next) {
+    // await this.tenantIsUse(ctx);
+    await next();
+  };
+};

+ 27 - 0
app/model/member/member.js

@@ -0,0 +1,27 @@
+'use strict';
+const Schema = require('mongoose').Schema;
+const moment = require('moment');
+const metaPlugin = require('naf-framework-mongoose-free/lib/model/meta-plugin');
+const { ObjectId } = require('mongoose').Types;
+// 会员表
+const member = {
+  name: { type: String }, // 姓名
+  gender: { type: String }, // 性别
+  phone: { type: String }, // 联系电话
+  money: { type: Number }, // 当前余额
+  points: { type: Number }, // 积分
+  openid: { type: String }, // 微信id
+  remark: { type: String },
+};
+const schema = new Schema(member, { toJSON: { virtuals: true } });
+schema.index({ id: 1 });
+schema.index({ name: 1 });
+schema.index({ gender: 1 });
+schema.index({ phone: 1 });
+schema.index({ openid: 1 });
+schema.index({ 'meta.createdAt': 1 });
+schema.plugin(metaPlugin);
+module.exports = app => {
+  const { mongoose } = app;
+  return mongoose.model('Member', schema, 'member');
+};

+ 24 - 0
app/model/member/points_record.js

@@ -0,0 +1,24 @@
+'use strict';
+const Schema = require('mongoose').Schema;
+const moment = require('moment');
+const metaPlugin = require('naf-framework-mongoose-free/lib/model/meta-plugin');
+const { ObjectId } = require('mongoose').Types;
+// 会员积分记录表
+const points_record = {
+  openid: { type: String }, // 微信id
+  points: { type: Number, default: 0 }, // 积分
+  before_points: { type: Number, default: 0 }, // 处理前的积分
+  after_points: { type: Number, default: 0 }, // 处理前的积分
+  opera: { type: String }, // 操作积分的操作; 0-购买商品(获得积分) 1-兑换折扣(消耗积分) 2-兑换物品(消耗积分)
+  opera_target: { type: String }, // 购买商品-订单id, 兑换折扣-订单id,兑换商品-商品id
+  remark: { type: String },
+};
+const schema = new Schema(points_record, { toJSON: { virtuals: true } });
+schema.index({ id: 1 });
+schema.index({ openid: 1 });
+schema.index({ 'meta.createdAt': 1 });
+schema.plugin(metaPlugin);
+module.exports = app => {
+  const { mongoose } = app;
+  return mongoose.model('Points_record', schema, 'points_record');
+};

+ 26 - 0
app/model/member/save_record.js

@@ -0,0 +1,26 @@
+'use strict';
+const Schema = require('mongoose').Schema;
+const moment = require('moment');
+const metaPlugin = require('naf-framework-mongoose-free/lib/model/meta-plugin');
+const { ObjectId } = require('mongoose').Types;
+// 会员充值表
+const save_record = {
+  openid: { type: String }, // 微信id
+  money: { type: Number, default: 0 }, // 充值金额
+  before_save: { type: Number, default: 0 }, // 充值前金额
+  after_save: { type: Number, default: 0 }, // 充值后金额
+  wx_order_no: { type: String }, // 微信订单号
+  status: { type: String, default: '0' }, // 0-待支付;1完成
+  remark: { type: String },
+};
+const schema = new Schema(save_record, { toJSON: { virtuals: true } });
+schema.index({ id: 1 });
+schema.index({ openid: 1 });
+schema.index({ wx_order_no: 1 });
+schema.index({ status: 1 });
+schema.index({ 'meta.createdAt': 1 });
+schema.plugin(metaPlugin);
+module.exports = app => {
+  const { mongoose } = app;
+  return mongoose.model('Save_record', schema, 'save_record');
+};

+ 30 - 0
app/model/system/admin.js

@@ -0,0 +1,30 @@
+'use strict';
+const Schema = require('mongoose').Schema;
+const moment = require('moment');
+const metaPlugin = require('naf-framework-mongoose-free/lib/model/meta-plugin');
+const { Secret } = require('naf-framework-mongoose-free/lib/model/schema');
+const { ObjectId } = require('mongoose').Types;
+// 表
+const admin = {
+  name: { type: String, default: '管理员' }, // 昵称
+  login_id: { type: String }, // 登陆用户名
+  password: { type: Secret, select: false }, // 密码
+  menu: { type: Array }, // 菜单(管理员默认全有),如果里面不为空,或者有内容了,就按内容换菜单
+  is_root: { type: Boolean, default: false }, // 为之后用,这个管理员是不是拥有最高权限:可不可以更改菜单或者其他操作
+  openid: { type: String }, // 微信openid
+  status: { type: String, default: '0' }, // 0:使用中;-1:禁用
+  remark: { type: String },
+};
+const schema = new Schema(admin, { 'multi-tenancy': true, toJSON: { virtuals: true } });
+schema.index({ id: 1 });
+schema.index({ name: 1 });
+schema.index({ login_id: 1 });
+schema.index({ is_root: 1 });
+schema.index({ openid: 1 });
+schema.index({ status: 1 });
+schema.index({ 'meta.createdAt': 1 });
+schema.plugin(metaPlugin);
+module.exports = app => {
+  const { mongoose } = app;
+  return mongoose.model('Admin', schema, 'admin');
+};

+ 25 - 0
app/model/system/menu.js

@@ -0,0 +1,25 @@
+'use strict';
+const Schema = require('mongoose').Schema;
+const metaPlugin = require('naf-framework-mongoose-free/lib/model/meta-plugin');
+const { ObjectId } = require('mongoose').Types;
+
+const Menu = {
+  title: { type: String, required: true }, // 菜单标题
+  route: { type: String, required: false }, // 路由
+  pid: { type: ObjectId, required: false }, // 上级id
+  disabled: { type: Boolean, default: false }, // 禁用状态
+  sort: { type: Number, required: false, default: 0 }, // 顺序,默认为0,使用降序排序,数值越大在前面
+  can_delete: { type: Boolean, default: true }, // 可以删除
+  params: { type: Object }, // 参数字段,需要就用,不需要就不用
+  icon: { type: String }, // 图标
+  remark: { type: String },
+};
+const schema = new Schema(Menu, {
+  toJSON: { virtuals: true },
+});
+schema.index({ id: 1 });
+schema.plugin(metaPlugin);
+module.exports = app => {
+  const { mongoose } = app;
+  return mongoose.model('Menu', schema, 'menu');
+};

+ 26 - 0
app/model/system/tenant.js

@@ -0,0 +1,26 @@
+'use strict';
+const Schema = require('mongoose').Schema;
+const moment = require('moment');
+const metaPlugin = require('naf-framework-mongoose-free/lib/model/meta-plugin');
+const { ObjectId } = require('mongoose').Types;
+// 表
+const tenant = {
+  _tenant: { type: String, require: true }, // 站点
+  name: { type: String }, // 站点名称
+  params: { type: Object }, // 参数
+  is_use: { type: Boolean, default: true }, // 是否使用
+  img: { type: Object }, // 图片设置
+  menus: { type: Array }, // 分站的权限菜单
+  remark: { type: String },
+};
+const schema = new Schema(tenant, { toJSON: { virtuals: true } });
+schema.index({ id: 1 });
+schema.index({ _tenant: 1 });
+schema.index({ name: 1 });
+schema.index({ is_use: 1 });
+schema.index({ 'meta.createdAt': 1 });
+schema.plugin(metaPlugin);
+module.exports = app => {
+  const { mongoose } = app;
+  return mongoose.model('Tenant', schema, 'tenant');
+};

+ 16 - 0
app/router.js

@@ -0,0 +1,16 @@
+'use strict';
+
+/**
+ * @param {Egg.Application} app - egg application
+ */
+module.exports = app => {
+  const { router, controller } = app;
+  router.get('/', controller.home.index);
+  router.get('/api/online', controller.chat.online);
+  router.post('/api/send', controller.chat.send);
+  router.put('/api/install', controller.home.install);
+  require('./z_router/system/admin')(app); // 管理员
+  require('./z_router/system/weixin')(app); // 微信相关
+  require('./z_router/system/tenant')(app); // 站点管理
+  require('./z_router/system/menu')(app); // 权限管理
+};

+ 33 - 0
app/service/goods/index.js

@@ -0,0 +1,33 @@
+'use strict';
+const { CrudService } = require('naf-framework-mongoose-free/lib/service');
+const { BusinessError, ErrorCode } = require('naf-core').Error;
+const _ = require('lodash');
+const assert = require('assert');
+
+//
+class IndexService extends CrudService {
+  constructor(ctx) {
+    super(ctx, 'index');
+    // this.model = this.ctx.model.Goods.Goods;
+  }
+
+  /**
+   * TODO
+   * 将地址传来,然后读取数据,将数据整理,存起来
+   * @param {Object} body 参数
+   */
+  async importGoods(body) {
+    console.log('in function:importGoods');
+  }
+
+  /**
+   * TOOD
+   * 导出货物数据
+   * @param {Object} body 参数
+   */
+  async exportGoods(body) {
+    console.log('in function:exportGoods');
+  }
+}
+
+module.exports = IndexService;

+ 83 - 0
app/service/install.js

@@ -0,0 +1,83 @@
+'use strict';
+const { CrudService } = require('naf-framework-mongoose-free/lib/service');
+const { BusinessError, ErrorCode } = require('naf-core').Error;
+const _ = require('lodash');
+const assert = require('assert');
+
+// 初始化项目相关
+class InstallService extends CrudService {
+  constructor(ctx) {
+    super(ctx, 'install');
+    this.admin = this.ctx.model.System.Admin;
+    this._tenant = this.ctx.model.System.Tenant;
+    this.menu = this.ctx.model.System.Menu;
+  }
+  /**
+   * 初始化
+   */
+  async index() {
+    console.log('in function:install');
+    // TODO,初始化管理员之类的
+    // this.adminInit();
+    // this.tenantInit();
+    this.menuInit();
+    this.tenantMenu();
+  }
+
+  /**
+   * 管理员初始化,生成最高权限----root管理员,_tenant:分站标识,master是全站最高
+   */
+  async adminInit() {
+    const has = await this.admin.count({ _tenant: 'master', is_root: true });
+    if (has > 0) return;
+    const data = {
+      name: '总管理员',
+      login_id: 'admin',
+      password: {
+        secret: '123456',
+      },
+      is_root: true,
+    };
+    await this.admin.create(data);
+  }
+
+  /**
+   * 站点列表初始化
+   */
+  async tenantInit() {
+    const has = await this._tenant.count({ _tenant: 'master' });
+    if (has > 0) return;
+    const data = {
+      _tenant: 'master',
+      name: '总管理站',
+    };
+    await this._tenant.create(data);
+  }
+
+  /**
+   * 权限列表初始化
+   */
+  async menuInit() {
+    const arr = [
+      { title: '首页', disabled: false, sort: 999, route: '/', icon: 'el-icon-s-grid', can_delete: false },
+      { title: '系统用户管理', disabled: false, sort: 990, route: '/users', icon: 'el-icon-user', can_delete: false },
+      { title: '权限管理', disabled: false, sort: 980, route: '/menus', icon: 'el-icon-menu', can_delete: false },
+      { title: '站点设置', disabled: false, sort: 970, route: '/setting', icon: 'el-icon-setting', can_delete: false },
+    ];
+    await this.menu.insertMany(arr);
+  }
+
+  /**
+   * 初始化分站菜单
+   */
+  async tenantMenu() {
+    const m_tenant = await this._tenant.findOne({ _tenant: 'master' });
+    const menus = await this.menu.find({ can_delete: false });
+    if (menus.length <= 0) return;
+    const menus_ids = menus.map(i => JSON.parse(JSON.stringify(i._id)));
+    m_tenant.menus = menus_ids;
+    await m_tenant.save();
+  }
+}
+
+module.exports = InstallService;

+ 15 - 0
app/service/member/member.js

@@ -0,0 +1,15 @@
+'use strict';
+const { CrudService } = require('naf-framework-mongoose-free/lib/service');
+const { BusinessError, ErrorCode } = require('naf-core').Error;
+const _ = require('lodash');
+const assert = require('assert');
+
+// 会员表
+class MemberService extends CrudService {
+  constructor(ctx) {
+    super(ctx, 'member');
+    this.model = this.ctx.model.Member.Member;
+  }
+}
+
+module.exports = MemberService;

+ 39 - 0
app/service/system/admin.js

@@ -0,0 +1,39 @@
+'use strict';
+const { CrudService } = require('naf-framework-mongoose-free/lib/service');
+const { BusinessError, ErrorCode } = require('naf-core').Error;
+const _ = require('lodash');
+const assert = require('assert');
+const jwt = require('jsonwebtoken');
+
+// 管理员
+class AdminService extends CrudService {
+  constructor(ctx) {
+    super(ctx, 'admin');
+    this.model = this.ctx.model.System.Admin;
+  }
+
+  /**
+   * 管理员登陆
+   * @param {Object} body
+   * @property login_id 登陆用户名
+   * @property password 密码
+   */
+  async login({ login_id, password }) {
+    const user = await this.model.findOne({ login_id }, '+password');
+    if (!user) { throw new BusinessError(ErrorCode.USER_NOT_EXIST, '管理员不存在'); }
+    const { secret } = user.password;
+    if (secret !== password) { throw new BusinessError(ErrorCode.BAD_PASSWORD, '密码错误'); }
+    const data = _.omit(JSON.parse(JSON.stringify(user)), [
+      'meta',
+      'passwd',
+      '__v',
+      'password',
+    ]);
+    console.log(data);
+    const { secret: secrets } = this.config.jwt;
+    const token = jwt.sign(data, secrets);
+    return token;
+  }
+}
+
+module.exports = AdminService;

+ 80 - 0
app/service/system/menu.js

@@ -0,0 +1,80 @@
+'use strict';
+
+const assert = require('assert');
+const _ = require('lodash');
+const moment = require('moment');
+const { ObjectId } = require('mongoose').Types;
+const { CrudService } = require('naf-framework-mongoose-free/lib/service');
+const { BusinessError, ErrorCode } = require('naf-core').Error;
+
+class MenuService extends CrudService {
+  constructor(ctx) {
+    super(ctx, 'menu');
+    this.model = this.ctx.model.System.Menu;
+    this._tenant = this.ctx.model.System.Tenant;
+    this.admin = this.ctx.model.System.Admin;
+  }
+
+  async delete({ id }) {
+    let children = await this.dbFindChildren(id);
+    children = children.map(i => ObjectId(i._id));
+    children.push(ObjectId(id));
+    const res = await this.model.deleteMany({ _id: children });
+    return res;
+  }
+
+  async dbFindChildren(pid) {
+    let toReturn = [];
+    const res = await this.model.find({ pid }, '_id');
+    toReturn = [ ...toReturn, ...res ];
+    if (res.length > 0) {
+      for (const i of res) {
+        const { _id } = i;
+        const list = await this.dbFindChildren(_id);
+        toReturn = [ ...toReturn, ...list ];
+      }
+    }
+    return toReturn;
+  }
+
+  async findProject() {
+    const res = await this.model.find().sort({ sort: -1 });
+    let list = [];
+    if (res && res.length > 0) {
+      list = await this.toFindChildren(res);
+    }
+    return list;
+  }
+
+  toFindChildren(list) {
+    list = JSON.parse(JSON.stringify(list));
+    const head = list.filter(f => !f.pid);
+    if (head.length <= 0) throw new BusinessError(ErrorCode.DATA_INVALID, '需要将根目录加入整合函数的参数中');
+    return this.findChildren(head, list);
+  }
+
+  findChildren(list, data) {
+    for (const i of list) {
+      let children = data.filter(f => f.pid === i._id);
+      if (children.length > 0) children = this.findChildren(children, data);
+      i.children = children;
+    }
+    return list;
+  }
+
+  async getUserMenu({ id }) {
+    const user = await this.admin.findById(id);
+    if (!user) throw new BusinessError(ErrorCode.USER_NOT_EXIST, '未找到用户信息!');
+    const { _tenant } = user;
+    if (!_tenant) throw new BusinessError(ErrorCode.DATA_INVALID, '未找到用户分站信息');
+    const tenant = await this._tenant.findOne({ _tenant });
+    if (!tenant) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到用户所在分站的信息');
+    const { menus } = tenant;
+    if (menus.length <= 0) throw new BusinessError(ErrorCode.DATA_INVALID, '未赋予用户任何权限');
+    const mList = await this.model.find({ _id: menus.map(i => ObjectId(i)) });
+    return this.toFindChildren(mList);
+
+  }
+}
+
+module.exports = MenuService;

+ 25 - 0
app/service/system/tenant.js

@@ -0,0 +1,25 @@
+'use strict';
+const { CrudService } = require('naf-framework-mongoose-free/lib/service');
+const { BusinessError, ErrorCode } = require('naf-core').Error;
+const _ = require('lodash');
+const assert = require('assert');
+
+// 站点
+class TenantService extends CrudService {
+  constructor(ctx) {
+    super(ctx, 'tenant');
+    this.model = this.ctx.model.System.Tenant;
+  }
+
+  /**
+   * 获取分站设置
+   * @param {Object} params
+   * @property _tenant 分站名
+   */
+  async getTenant({ _tenant }) {
+    const res = await this.model.findOne({ _tenant });
+    return res;
+  }
+}
+
+module.exports = TenantService;

+ 73 - 0
app/service/system/weixin.js

@@ -0,0 +1,73 @@
+'use strict';
+const { CrudService } = require('naf-framework-mongoose-free/lib/service');
+const { BusinessError, ErrorCode } = require('naf-core').Error;
+const _ = require('lodash');
+const assert = require('assert');
+const moment = require('moment');
+
+// 微信相关
+class WeixinService extends CrudService {
+  constructor(ctx) {
+    super(ctx, 'weixin');
+  }
+
+  /**
+   * 小程序登录(换取openid)
+   * @param {Object} body js_code 临时登陆码
+   * @param {String} tenant 分站
+   */
+  async appAuth({ js_code }, tenant) {
+    if (!tenant) throw new BusinessError(ErrorCode.ACCESS_DENIED, '未找到分站');
+    const { wxApp } = this.app.config;
+    if (!wxApp) throw new BusinessError(ErrorCode.SERVICE_FAULT, '系统未启动小程序登录!');
+    if (!wxApp[tenant]) throw new BusinessError(ErrorCode.SERVICE_FAULT, '未设置该小程序相关信息!');
+    const { appid, secret } = wxApp[tenant];
+    const url = `https://api.weixin.qq.com/sns/jscode2session?appid=${appid}&secret=${secret}&js_code=${js_code}&grant_type=authorization_code`;
+    const res = await this.ctx.curl(url, {
+      method: 'get',
+      headers: {
+        'content-type': 'application/json',
+      },
+      dataType: 'json',
+    });
+    const { openid } = res.data;
+    if (!openid) throw new BusinessError(ErrorCode.BUSINESS, '未获取到openid', '未获取到openid');
+    return res.data;
+  }
+
+  async getSecret({ encryptedData, iv, session_key }) {
+    assert(encryptedData, '缺少参数-encryptedData');
+    assert(iv, '缺少参数-iv');
+    assert(session_key, '缺少参数-session_key');
+    // 找到小程序设置,最后解密完,需要检验appid是不是对的
+    const { wxApp } = this.app.config;
+    const wxInfo = wxApp[this.ctx.tenant];
+    const crypto = require('crypto');
+    session_key = new Buffer(session_key, 'base64');
+    encryptedData = new Buffer(encryptedData, 'base64');
+    iv = new Buffer(iv, 'base64');
+    let decoded;
+    try {
+      const decipher = crypto.createDecipheriv('aes-128-cbc', session_key, iv);
+      decipher.setAutoPadding(true);
+      decoded = decipher.update(encryptedData, 'binary', 'utf8');
+      decoded += decipher.final('utf8');
+      decoded = JSON.parse(decoded);
+    } catch (error) {
+      throw new BusinessError(ErrorCode.DATA_INVALID, '密文不正确!');
+    }
+    if (decoded.watermark.appid !== wxInfo.appid) {
+      throw new BusinessError(ErrorCode.VERIFYCODE_INVALID, '未通过校验!');
+    }
+
+    decoded.stepInfoList = decoded.stepInfoList.map(i => {
+      i.date = moment.unix(i.timestamp).format('YYYY-MM-DD');
+      return i;
+    });
+    decoded.stepInfo = decoded.stepInfoList.find(f => f.date === moment().format('YYYY-MM-DD'));
+    delete decoded.stepInfoList;
+    return decoded;
+  }
+}
+
+module.exports = WeixinService;

+ 12 - 0
app/z_router/system/admin.js

@@ -0,0 +1,12 @@
+'use strict';
+
+
+module.exports = app => {
+  const { router, controller, config } = app;
+  const profix = `/api${config.appName ? `/${config.appName}` : ''}`;
+  const index = 'system';
+  const target = 'admin';
+  router.post(target, `${profix}/${index}/${target}/login`, controller[index][target].login);
+  router.resources(target, `${profix}/${index}/${target}`, controller[index][target]); // index、create、show、destroy
+  router.post(target, `${profix}/${index}/${target}/update/:id`, controller[index][target].update);
+};

+ 12 - 0
app/z_router/system/menu.js

@@ -0,0 +1,12 @@
+'use strict';
+
+
+module.exports = app => {
+  const { router, controller, config } = app;
+  const profix = `/api${config.appName ? `/${config.appName}` : ''}`;
+  const index = 'system';
+  const target = 'menu';
+  router.get(target, `${profix}/${index}/${target}/getMenu/:id`, controller[index][target].getUserMenu);
+  router.resources(target, `${profix}/${index}/${target}`, controller[index][target]); // index、create、show、destroy
+  router.post(target, `${profix}/${index}/${target}/update/:id`, controller[index][target].update);
+};

+ 13 - 0
app/z_router/system/tenant.js

@@ -0,0 +1,13 @@
+'use strict';
+
+
+module.exports = app => {
+  const { router, controller, config } = app;
+  const profix = `/api${config.appName ? `/${config.appName}` : ''}`;
+  const index = 'system';
+  const target = 'tenant';
+  const tc = app.middleware.tenantCheck();
+  router.get(target, `${profix}/${index}/${target}/getTenant/:_tenant`, controller[index][target].getTenant);
+  router.resources(target, `${profix}/${index}/${target}`, tc, controller[index][target]); // index、create、show、destroy
+  router.post(target, `${profix}/${index}/${target}/update/:id`, tc, controller[index][target].update);
+};

+ 11 - 0
app/z_router/system/weixin.js

@@ -0,0 +1,11 @@
+'use strict';
+
+
+module.exports = app => {
+  const { router, controller, config } = app;
+  const profix = `/api${config.appName ? `/${config.appName}` : ''}`;
+  const index = 'system';
+  const target = 'weixin';
+  router.get(target, `${profix}/${index}/${target}/appAuth`, controller[index][target].appAuth);
+  router.post(target, `${profix}/${index}/${target}/decrypt`, controller[index][target].decrypt);
+};

+ 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

+ 75 - 0
config/config.default.js

@@ -0,0 +1,75 @@
+/* eslint valid-jsdoc: "off" */
+
+'use strict';
+const { jwt } = require('./config.secret');
+
+/**
+ * @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 + '_1625713704561_5494';
+
+  // add your middleware config here
+  config.middleware = [];
+
+  // add your user config here
+  const userConfig = {
+    // myAppName: 'egg',
+  };
+
+  config.cluster = {
+    listen: {
+      port: 11111,
+    },
+  };
+  config.appName = 'market';
+
+  config.dbName = 'market';
+  config.mongoose = {
+    url: `mongodb://localhost:27017/${config.dbName}`,
+    options: {
+      // user: 'admin',
+      // pass: '111111',
+      // authSource: 'admin',
+      // useNewUrlParser: true,
+      // useCreateIndex: true,
+    },
+  };
+
+  config.amqp = {
+    client: {
+      hostname: '127.0.0.1',
+      username: 'test',
+      password: '123456',
+      vhost: 'test',
+    },
+    app: true,
+    agent: true,
+  };
+  config.jwt = {
+    ...jwt,
+    expiresIn: '1d',
+    issuer: 'market',
+  };
+
+  config.redis = {
+    client: {
+      port: 6379, // Redis port
+      host: '127.0.0.1', // Redis host
+      password: '123456',
+      db: 1,
+    },
+  };
+
+  return {
+    ...config,
+    ...userConfig,
+  };
+};

+ 7 - 0
config/config.secret.js

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

+ 15 - 0
config/plugin.js

@@ -0,0 +1,15 @@
+'use strict';
+
+/** @type Egg.EggPlugin */
+exports.amqp = {
+  enable: true,
+  package: 'egg-naf-amqp',
+};
+exports.redis = {
+  enable: true,
+  package: 'egg-redis',
+};
+
+exports.multiTenancy = {
+  enable: true,
+};

+ 54 - 0
package.json

@@ -0,0 +1,54 @@
+{
+  "name": "service",
+  "version": "1.0.0",
+  "description": "",
+  "private": true,
+  "egg": {
+    "framework": "naf-framework-mongoose-free"
+  },
+  "dependencies": {
+    "egg": "^2.15.1",
+    "egg-naf-amqp": "0.0.13",
+    "egg-redis": "^2.4.0",
+    "egg-scripts": "^2.11.0",
+    "jsonwebtoken": "^8.5.1",
+    "lodash": "^4.17.15",
+    "moment": "^2.29.1",
+    "naf-framework-mongoose-free": "0.0.2",
+    "string-random": "^0.1.3",
+    "url-join": "^4.0.1"
+  },
+  "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",
+    "stop": "egg-scripts stop --title=egg-server-service",
+    "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": "lrf",
+  "license": "MIT"
+}

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