lrf402788946 3 年之前
父節點
當前提交
ddba4c236f

+ 0 - 1
app.js

@@ -7,7 +7,6 @@ class AppBootHook {
   // 应用已启动阶段
   // 应用已启动阶段
   async didReady() {
   async didReady() {
     // 初始化数据
     // 初始化数据
-    console.log('in function:didReady');
     const ctx = await this.app.createAnonymousContext();
     const ctx = await this.app.createAnonymousContext();
     await ctx.service.install.index();
     await ctx.service.install.index();
   }
   }

+ 14 - 0
app/controller/dining/.order.js

@@ -37,5 +37,19 @@ module.exports = {
   },
   },
   useMeal: {
   useMeal: {
     params: ["!id"],
     params: ["!id"],
+    service: "useMeal",
+  },
+  mealCard: {
+    params: ["!openid"],
+    service: "mealCard",
+  },
+  getByOpenid: {
+    parameters: {
+      query: {
+        openid: "openid",
+        date: "date",
+      },
+    },
+    service: "getByOpenid",
   },
   },
 };
 };

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

@@ -13,5 +13,10 @@ class WeixinController extends Controller {
     const data = await this.service.appAuth(this.ctx.query, this.ctx.tenant);
     const data = await this.service.appAuth(this.ctx.query, this.ctx.tenant);
     this.ctx.ok({ data });
     this.ctx.ok({ data });
   }
   }
+
+  async decrypt() {
+    const data = await this.service.getSecret(this.ctx.request.body);
+    this.ctx.ok({ data });
+  }
 }
 }
 module.exports = CrudController(WeixinController, {});
 module.exports = CrudController(WeixinController, {});

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

@@ -10,7 +10,6 @@ const tenantIsUse = async ctx => {
 };
 };
 module.exports = options => {
 module.exports = options => {
   return async function tenantUse(ctx, next) {
   return async function tenantUse(ctx, next) {
-    console.log('function in tenant-use middleware');
     // await this.tenantIsUse(ctx);
     // await this.tenantIsUse(ctx);
     await next();
     await next();
   };
   };

+ 3 - 1
app/router/dining/order.js

@@ -6,7 +6,9 @@ module.exports = app => {
   const profix = `/api${config.appName ? `/${config.appName}` : ''}`;
   const profix = `/api${config.appName ? `/${config.appName}` : ''}`;
   const index = 'dining';
   const index = 'dining';
   const target = 'order';
   const target = 'order';
-  router.post(target, `${profix}/${index}/${target}/useMeal/:id`, controller[index][target].useMeal);
+  router.get(target, `${profix}/${index}/${target}/getByOpenid`, controller[index][target].getByOpenid);
+  router.get(target, `${profix}/${index}/${target}/useMeal/:id`, controller[index][target].useMeal);
+  router.post(target, `${profix}/${index}/${target}/mealCard/:openid`, controller[index][target].mealCard);
   router.resources(target, `${profix}/${index}/${target}`, controller[index][target]); // index、create、show、destroy
   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);
   router.post(target, `${profix}/${index}/${target}/update/:id`, controller[index][target].update);
 };
 };

+ 1 - 0
app/router/system/weixin.js

@@ -7,4 +7,5 @@ module.exports = app => {
   const index = 'system';
   const index = 'system';
   const target = 'weixin';
   const target = 'weixin';
   router.get(target, `${profix}/${index}/${target}/appAuth`, controller[index][target].appAuth);
   router.get(target, `${profix}/${index}/${target}/appAuth`, controller[index][target].appAuth);
+  router.post(target, `${profix}/${index}/${target}/decrypt`, controller[index][target].decrypt);
 };
 };

+ 127 - 6
app/service/dining/order.js

@@ -4,6 +4,7 @@ const { BusinessError, ErrorCode } = require('naf-core').Error;
 const _ = require('lodash');
 const _ = require('lodash');
 const assert = require('assert');
 const assert = require('assert');
 const moment = require('moment');
 const moment = require('moment');
+const { ObjectId } = require('mongoose').Types;
 // 点餐
 // 点餐
 class OrderService extends CrudService {
 class OrderService extends CrudService {
   constructor(ctx) {
   constructor(ctx) {
@@ -11,25 +12,143 @@ class OrderService extends CrudService {
     this.model = this.ctx.model.Dining.Order;
     this.model = this.ctx.model.Dining.Order;
   }
   }
   async create(data) {
   async create(data) {
+    const { date } = data;
+    if (!moment().isBefore(date)) throw new BusinessError(ErrorCode.BUSINESS, '只能预订明天及之后的用餐!');
     const res = await this.model.create(data);
     const res = await this.model.create(data);
     // 所有菜品,都需要去加点单量
     // 所有菜品,都需要去加点单量
     this.ctx.service.dining.menu.addOrder(data);
     this.ctx.service.dining.menu.addOrder(data);
     return res;
     return res;
   }
   }
+
+  /**
+   * 根据openid查某天的订单
+   * @param {Object} query ctx.query
+   * @property {String} openid
+   * @property {String} date 日期
+   * @return {Any} Object:该用户点的单;Null:没点单
+   */
+  async getByOpenid({ openid, date }) {
+    const res = await this.model.findOne({ openid, date });
+    return res;
+  }
+  /**
+   * 首页餐卡内容获取
+   * @param {Object} query ctx.query
+   * @property {String} openid 就是openid,微信的,没啥解释
+   * @return {Any} Object:返回哪一餐的内容/null
+   */
+  async mealCard({ openid }) {
+    const _tenant = this.ctx.tenant;
+    const site = await this.ctx.model.System.Tenant.findOne({ _tenant });
+    if (!site) { throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到分站信息!'); }
+    const { params } = site;
+    if (!params) { throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到分站参数设置'); }
+    const be = params.find(f => f.key === 'breakfast_end');
+    if (!be) {
+      throw new BusinessError(
+        ErrorCode.DATA_NOT_EXIST,
+        '未找到分站早餐结束时间设置'
+      );
+    }
+    const le = params.find(f => f.key === 'lunch_end');
+    if (!le) {
+      throw new BusinessError(
+        ErrorCode.DATA_NOT_EXIST,
+        '未找到分站午餐结束时间设置'
+      );
+    }
+    const de = params.find(f => f.key === 'dinner_end');
+    if (!de) {
+      throw new BusinessError(
+        ErrorCode.DATA_NOT_EXIST,
+        '未找到分站晚餐结束时间设置'
+      );
+    }
+    const date = moment().format('YYYY-MM-DD');
+    const arr = [
+      { key: 'breakfast', time: `${date} ${be.value}` },
+      { key: 'lunch', time: `${date} ${le.value}` },
+      { key: 'dinner', time: `${date} ${de.value}` },
+    ];
+    // 先判断是在 早饭结束前/午饭结束前/晚饭结束前/晚饭结束后
+    // 所有的结束前,都显示该顿饭即可, 只有晚饭结束后,显示的是明天早餐
+    let res;
+    for (const i of arr) {
+      const { key, time } = i;
+      const r = this.checkMealType(time, key);
+      if (r !== false) {
+        res = r;
+        break;
+      }
+    }
+    const query = { openid };
+    if (res) {
+      // 说明是今天的餐,请求今天的餐
+      query.date = date;
+    } else {
+      // 说明是明天早餐,请求明天的餐
+      query.date = moment().add(1, 'days').format('YYYY-MM-DD');
+      res = 'breakfast';
+    }
+    const order = await this.model.findOne(query);
+    if (!order) return null;
+    const meal = _.get(order, res);
+    return { data: meal, type: this.ctx.service.util.util.getZh(res) };
+  }
+
+  /**
+   * 判断是哪一餐
+   * @param {String} time 时间字符串格式YYYY-MM-DD HH:mm:ss
+   * @param {String} type 三餐类型
+   * @return {Any} String,确定是该类型的餐;Boolean,不是该类型的餐
+   */
+  checkMealType(time, type) {
+    const r = moment().isSameOrBefore(time);
+    if (r) return type;
+    return false;
+  }
+
   /**
   /**
    * 扫码领餐
    * 扫码领餐
    * @param {Object} query 参数
    * @param {Object} query 参数
    * @property id 早/中/晚餐的数据id,子文档id
    * @property id 早/中/晚餐的数据id,子文档id
    */
    */
   async useMeal({ id }) {
   async useMeal({ id }) {
-    console.log(id);
+    id = ObjectId(id);
+    console.log(this.ctx.tenant);
+    const query = {
+      $or: [
+        { 'breakfast._id': id },
+        { 'lunch._id': id },
+        { 'dinner._id': id },
+      ],
+    };
+    let order = await this.model.findOne(query);
+    if (order) {
+      order = this.checkOrder(id, order, 'breakfast');
+      order = this.checkOrder(id, order, 'lunch');
+      order = this.checkOrder(id, order, 'dinner');
+      return await order.save();
+    }
+    throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到该餐的数据!');
+  }
+
+  checkOrder(id, order, type) {
+    if (ObjectId(id).equals(order[type]._id)) {
+      if (order[type].is_use === '1' || order[type].is_use === '2') throw new BusinessError(ErrorCode.DATA_INVALID, '该餐状已经使用');
+      else if (order[type].is_use === '3') throw new BusinessError(ErrorCode.DATA_INVALID, '该餐状已失效');
+      else order[type].is_use = '2';
+    }
+    return order;
   }
   }
 
 
   /**
   /**
    * 检查所有的票是否过期
    * 检查所有的票是否过期
    */
    */
   async checkTimeOut() {
   async checkTimeOut() {
-    const tenantList = await this.ctx.model.System.Tenant.find({ _tenant: { $ne: 'master' } });
+    const tenantList = await this.ctx.model.System.Tenant.find({
+      _tenant: { $ne: 'master' },
+    });
     for (const site of tenantList) {
     for (const site of tenantList) {
       const { _tenant, params } = site;
       const { _tenant, params } = site;
       const pbe = params.find(f => f.key === 'breakfast_end');
       const pbe = params.find(f => f.key === 'breakfast_end');
@@ -48,13 +167,15 @@ class OrderService extends CrudService {
         const breakfast_time = `${date} ${pbe.value}`;
         const breakfast_time = `${date} ${pbe.value}`;
         const lunch_time = `${date} ${ple.value}`;
         const lunch_time = `${date} ${ple.value}`;
         const dinner_time = `${date} ${pde.value}`;
         const dinner_time = `${date} ${pde.value}`;
-        if (order.breakfast.list.length > 0 && moment().isSameOrAfter(breakfast_time)) order.breakfast.is_use = '3';
-        if (order.lunch.list.length > 0 && moment().isSameOrAfter(lunch_time)) order.lunch.is_use = '3';
-        if (order.dinner.list.length > 0 && moment().isSameOrAfter(dinner_time)) order.dinner.is_use = '3';
+        if (
+          order.breakfast.list.length > 0 &&
+          moment().isSameOrAfter(breakfast_time)
+        ) { order.breakfast.is_use = '3'; }
+        if (order.lunch.list.length > 0 && moment().isSameOrAfter(lunch_time)) { order.lunch.is_use = '3'; }
+        if (order.dinner.list.length > 0 && moment().isSameOrAfter(dinner_time)) { order.dinner.is_use = '3'; }
         await order.save();
         await order.save();
       }
       }
     }
     }
-
   }
   }
 }
 }
 
 

+ 0 - 2
app/service/install.js

@@ -15,8 +15,6 @@ class InstallService extends CrudService {
    * 初始化
    * 初始化
    */
    */
   async index() {
   async index() {
-    console.log('in function:in init');
-    console.error('in function:in init');
     // TODO,初始化管理员之类的
     // TODO,初始化管理员之类的
     this.adminInit();
     this.adminInit();
     this.tenantInit();
     this.tenantInit();

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

@@ -3,6 +3,7 @@ const { CrudService } = require('naf-framework-mongoose/lib/service');
 const { BusinessError, ErrorCode } = require('naf-core').Error;
 const { BusinessError, ErrorCode } = require('naf-core').Error;
 const _ = require('lodash');
 const _ = require('lodash');
 const assert = require('assert');
 const assert = require('assert');
+const moment = require('moment');
 
 
 // 微信相关
 // 微信相关
 class WeixinService extends CrudService {
 class WeixinService extends CrudService {
@@ -10,6 +11,11 @@ class WeixinService extends CrudService {
     super(ctx, 'weixin');
     super(ctx, 'weixin');
   }
   }
 
 
+  /**
+   * 小程序登录(换取openid)
+   * @param {Object} body js_code 临时登陆码
+   * @param {String} tenant 分站
+   */
   async appAuth({ js_code }, tenant) {
   async appAuth({ js_code }, tenant) {
     if (!tenant) throw new BusinessError(ErrorCode.ACCESS_DENIED, '未找到分站');
     if (!tenant) throw new BusinessError(ErrorCode.ACCESS_DENIED, '未找到分站');
     const { wxApp } = this.app.config;
     const { wxApp } = this.app.config;
@@ -28,6 +34,40 @@ class WeixinService extends CrudService {
     if (!openid) throw new BusinessError(ErrorCode.BUSINESS, '未获取到openid', '未获取到openid');
     if (!openid) throw new BusinessError(ErrorCode.BUSINESS, '未获取到openid', '未获取到openid');
     return res.data;
     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;
 module.exports = WeixinService;

+ 10 - 21
app/service/util/util.js

@@ -68,27 +68,6 @@ class UtilService extends CrudService {
     await workbook.xlsx.writeFile(filepath);
     await workbook.xlsx.writeFile(filepath);
   }
   }
 
 
-  getHeader() {
-    return [
-      { key: 'name', label: '用户姓名' },
-      { key: 'phone', label: '联系电话' },
-      { key: 'education', label: '最高学历' },
-      { key: 'school', label: '毕业学校' },
-      { key: 'birthDate', label: '出生日期' },
-      { key: 'email', label: '电子邮箱' },
-      { key: 'qqwx', label: 'QQ/微信' },
-      { key: 'company', label: '工作单位' },
-      { key: 'zwzc', label: '职务职称' },
-      { key: 'expertise', label: '擅长领域' },
-      { key: 'workexperience', label: '工作经历' },
-      { key: 'scientific', label: '科研综述' },
-      { key: 'undertakingproject', label: '承担项目' },
-      { key: 'scienceaward', label: '科技奖励' },
-      { key: 'social', label: '社会任职' },
-      { key: 'img_path', label: '用户头像' },
-    ];
-  }
-
   dealQuery(query) {
   dealQuery(query) {
     return this.turnFilter(this.turnDateRangeQuery(query));
     return this.turnFilter(this.turnDateRangeQuery(query));
   }
   }
@@ -140,5 +119,15 @@ class UtilService extends CrudService {
     return filter;
     return filter;
   }
   }
 
 
+  getZh(en) {
+    const arr = [
+      { en: 'breakfast', zh: '早餐' },
+      { en: 'lunch', zh: '午餐' },
+      { en: 'dinner', zh: '晚餐' },
+    ];
+    const r = arr.find(f => f.en === en);
+    if (r) return r.zh;
+  }
+
 }
 }
 module.exports = UtilService;
 module.exports = UtilService;

+ 35 - 0
test/WXBizDataCrypt.js

@@ -0,0 +1,35 @@
+'use strict';
+const crypto = require('crypto');
+
+function WXBizDataCrypt(appId, sessionKey) {
+  this.appId = appId;
+  this.sessionKey = sessionKey;
+}
+
+WXBizDataCrypt.prototype.decryptData = function(encryptedData, iv) {
+  // base64 decode
+  const sessionKey = new Buffer(this.sessionKey, 'base64');
+  encryptedData = new Buffer(encryptedData, 'base64');
+  iv = new Buffer(iv, 'base64');
+  let decoded;
+  try {
+    // 解密
+    const decipher = crypto.createDecipheriv('aes-128-cbc', sessionKey, iv);
+    // 设置自动 padding 为 true,删除填充补位
+    decipher.setAutoPadding(true);
+    decoded = decipher.update(encryptedData, 'binary', 'utf8');
+    decoded += decipher.final('utf8');
+
+    decoded = JSON.parse(decoded);
+  } catch (err) {
+    throw new Error('Illegal Buffer');
+  }
+
+  if (decoded.watermark.appid !== this.appId) {
+    throw new Error('Illegal Buffer');
+  }
+
+  return decoded;
+};
+
+module.exports = WXBizDataCrypt;

+ 46 - 0
test/demo.js

@@ -0,0 +1,46 @@
+'use strict';
+const WXBizDataCrypt = require('./WXBizDataCrypt');
+
+const appId = 'wx4f4bc4dec97d474b';
+const sessionKey = 'tiihtNczf5v6AKRyjwEUhQ==';
+const encryptedData =
+	'CiyLU1Aw2KjvrjMdj8YKliAjtP4gsMZM' +
+	'QmRzooG2xrDcvSnxIMXFufNstNGTyaGS' +
+	'9uT5geRa0W4oTOb1WT7fJlAC+oNPdbB+' +
+	'3hVbJSRgv+4lGOETKUQz6OYStslQ142d' +
+	'NCuabNPGBzlooOmB231qMM85d2/fV6Ch' +
+	'evvXvQP8Hkue1poOFtnEtpyxVLW1zAo6' +
+	'/1Xx1COxFvrc2d7UL/lmHInNlxuacJXw' +
+	'u0fjpXfz/YqYzBIBzD6WUfTIF9GRHpOn' +
+	'/Hz7saL8xz+W//FRAUid1OksQaQx4CMs' +
+	'8LOddcQhULW4ucetDf96JcR3g0gfRK4P' +
+	'C7E/r7Z6xNrXd2UIeorGj5Ef7b1pJAYB' +
+	'6Y5anaHqZ9J6nKEBvB4DnNLIVWSgARns' +
+	'/8wR2SiRS7MNACwTyrGvt9ts8p12PKFd' +
+	'lqYTopNHR1Vf7XjfhQlVsAJdNiKdYmYV' +
+	'oKlaRv85IfVunYzO0IKXsyl7JCUjCpoG' +
+	'20f0a04COwfneQAGGwd5oa+T8yO5hzuy' +
+	'Db/XcxxmK01EpqOyuxINew==';
+const iv = 'r7BXXKkLb8qrSNn05n0qiA==';
+
+const pc = new WXBizDataCrypt(appId, sessionKey);
+
+const data = pc.decryptData(encryptedData, iv);
+
+console.log('解密后 data: ', data);
+// 解密后的数据为
+//
+// data = {
+//   "nickName": "Band",
+//   "gender": 1,
+//   "language": "zh_CN",
+//   "city": "Guangzhou",
+//   "province": "Guangdong",
+//   "country": "CN",
+//   "avatarUrl": "http://wx.qlogo.cn/mmopen/vi_32/aSKcBBPpibyKNicHNTMM0qJVh8Kjgiak2AHWr8MHM4WgMEm7GFhsf8OYrySdbvAMvTsw3mo8ibKicsnfN5pRjl1p8HQ/0",
+//   "unionId": "ocMvos6NjeKLIBqg5Mr9QjxrP1FA",
+//   "watermark": {
+//     "timestamp": 1477314187,
+//     "appid": "wx4f4bc4dec97d474b"
+//   }
+// }