lrf 1 year ago
commit
9007ab4fe1
63 changed files with 4109 additions and 0 deletions
  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. 13 0
      README.md
  8. 27 0
      app.js
  9. 12 0
      app/controller/.login.js
  10. 20 0
      app/controller/home.js
  11. 29 0
      app/controller/import.js
  12. 13 0
      app/controller/login.js
  13. 72 0
      app/controller/usual.js
  14. 40 0
      app/controller/wx.js
  15. 29 0
      app/middleware/killColumns.js
  16. 37 0
      app/middleware/levelSearch.js
  17. 117 0
      app/middleware/operaLogs.js
  18. 39 0
      app/middleware/securityGuard/housePoliceCode.js
  19. 72 0
      app/middleware/tokenCheck.js
  20. 21 0
      app/model/wxUserCache.js
  21. 56 0
      app/public/dataToDb.js
  22. 31 0
      app/public/dbToData.js
  23. 50 0
      app/public/guardWorkMeta.js
  24. 950 0
      app/public/importSetting.js
  25. 47 0
      app/public/query.js
  26. 63 0
      app/public/securityGuardImportMeta.js
  27. 50 0
      app/public/tableName.js
  28. 14 0
      app/router.js
  29. 18 0
      app/schedule/wxCache.js
  30. 44 0
      app/service/create.js
  31. 32 0
      app/service/delete.js
  32. 49 0
      app/service/fetch.js
  33. 666 0
      app/service/import.js
  34. 112 0
      app/service/login.js
  35. 88 0
      app/service/query.js
  36. 28 0
      app/service/securityGuard/base.js
  37. 51 0
      app/service/securityGuard/work.js
  38. 135 0
      app/service/special.js
  39. 33 0
      app/service/statis.js
  40. 46 0
      app/service/update.js
  41. 70 0
      app/service/usual.js
  42. 105 0
      app/service/util/http-util.js
  43. 72 0
      app/service/util/rabbitMq.js
  44. 88 0
      app/service/util/util.js
  45. 185 0
      app/service/wx.js
  46. 12 0
      app/z_router/import.js
  47. 10 0
      app/z_router/login.js
  48. 24 0
      app/z_router/usual.js
  49. 13 0
      app/z_router/wx.js
  50. 14 0
      appveyor.yml
  51. 101 0
      config/config.default.js
  52. 20 0
      config/config.prod.js
  53. 7 0
      config/config.secret.js
  54. 9 0
      config/plugin.js
  55. 17 0
      ecosystem.config.js
  56. 5 0
      jsconfig.json
  57. 54 0
      package.json
  58. 9 0
      server.js
  59. 20 0
      test/app/controller/home.test.js
  60. 10 0
      test/test.js
  61. 3 0
      todo.md
  62. 39 0
      目录结构.md
  63. 17 0
      码代码注意.md

+ 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

+ 13 - 0
README.md

@@ -0,0 +1,13 @@
+# server-baoan
+
+保安服务端
+
+# 看其他的.md文件比这个有用多了
+## 本地运行: npm run dev
+## 服务器使用 pm2 管理,常用指令:
+ 1. pm2 start :在项目的根目录,就是能看到 `ecosystem.config.js` 和 `server.js` 的地方用
+ 2. pm2 stop xxx:停止pm2管理的某个项目 xxx可以写项目名(`ecosystem.config.js文件中的app就是这个项目在pm2的任务名称`),可以写自动排的编号,在列表里都能看到.不限制使用的地方
+ 3. pm2 list :查看pm2运行中的项目列表
+ 4. 更多指令自行去官网找
+
+## 如果没有mongodb,直接在config.default中注释掉关于mongodb的配置即可

+ 27 - 0
app.js

@@ -0,0 +1,27 @@
+'use strict';
+class AppBootHook {
+  constructor(app) {
+    this.app = app;
+  }
+
+  configWillLoad() {
+    const mongoConfig = this.app.config.mongoose;
+    const mongoClient = require('mongodb').MongoClient;
+    const connectUrl = `mongodb://${mongoConfig.options.user}:${mongoConfig.options.pass}@localhost:27017/wxCache?authSource=admin`;
+    mongoClient.connect(connectUrl, (err, db) => {
+      if (db) this.app.config.canUseMongoDb = true;
+      else this.app.config.canUseMongoDb = false;
+    });
+  }
+  async didLoad() {
+    // console.log(this.app);
+  }
+
+  async willReady() {
+    // 所有的插件都已启动完毕,但是应用整体还未 ready
+    // 可以做一些数据初始化等操作,这些操作成功才会启动应用
+
+  }
+
+}
+module.exports = AppBootHook;

+ 12 - 0
app/controller/.login.js

@@ -0,0 +1,12 @@
+module.exports = {
+  login: {
+    parameters: {
+      params: ["!table"],
+    },
+    requestBody: ["phone", "password", "openid", "wx"],
+  },
+  checkToken: {
+    requestBody: ["!token"],
+    service: "checkJwt",
+  },
+};

+ 20 - 0
app/controller/home.js

@@ -0,0 +1,20 @@
+'use strict';
+
+const Controller = require('egg').Controller;
+const _ = require('lodash');
+
+class HomeController extends Controller {
+  async index() {
+    const { ctx } = this;
+    ctx.body = 'hi, egg';
+
+  }
+
+  async util() {
+    // const data = await this.ctx.service.wx.updateCache();
+    console.log(this.app.config.canUseMongoDb);
+    this.ctx.ok({ });
+  }
+}
+
+module.exports = HomeController;

+ 29 - 0
app/controller/import.js

@@ -0,0 +1,29 @@
+'use strict';
+const Controller = require('egg').Controller;
+const { CrudController } = require('naf-framework-mongoose-free/lib/controller');
+
+//
+class ImportController extends Controller {
+  constructor(ctx) {
+    super(ctx);
+    this.service = this.ctx.service.import;
+  }
+  async examinee() {
+    const data = await this.service.updateExaminee(this.ctx.request.body);
+    this.ctx.ok(data);
+  }
+  async staff() {
+    const data = await this.service.staffBase(this.ctx.request.body);
+    this.ctx.ok({ data });
+  }
+
+  async securityGuard() {
+    const data = await this.service.securityGuard(this.ctx.request.body);
+    this.ctx.ok(data);
+  }
+  async guardWork() {
+    const data = await this.service.guardWork(this.ctx.request.body);
+    this.ctx.ok(data);
+  }
+}
+module.exports = CrudController(ImportController, {});

+ 13 - 0
app/controller/login.js

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

+ 72 - 0
app/controller/usual.js

@@ -0,0 +1,72 @@
+'use strict';
+const Controller = require('egg').Controller;
+
+// 通常接口
+class UsualController extends Controller {
+  constructor(ctx) {
+    super(ctx);
+    this.params = this.ctx.params;
+    this.queryObject = this.ctx.request.query;
+    this.service = this.ctx.service.usual;
+    this.reqBody = this.ctx.request.body;
+    this.queryService = this.ctx.service.query;
+    this.fetchService = this.ctx.service.fetch;
+    this.createService = this.ctx.service.create;
+    this.updateService = this.ctx.service.update;
+    this.deleteService = this.ctx.service.delete;
+    this.importService = this.ctx.service.import;
+    this.statisService = this.ctx.service.statis;
+  }
+  // 通用多查
+  async query() {
+    const data = await this.queryService.index(this.params, this.queryObject);
+    this.ctx.ok(data);
+  }
+  // 通用单查
+  async fetch() {
+    const data = await this.fetchService.index(this.params, this.queryObject);
+    this.ctx.ok(data);
+  }
+  // 通用创建
+  async create() {
+    const data = await this.createService.index(this.params, this.reqBody);
+    this.ctx.ok(data);
+  }
+  // 通用修改
+  async update() {
+    const data = await this.updateService.index(this.params, this.queryObject, this.reqBody);
+    this.ctx.ok(data);
+  }
+  // 通用删除
+  async delete() {
+    const data = await this.deleteService.index(this.params, this.queryObject);
+    this.ctx.ok(data);
+  }
+  // 通用导入
+  async import() {
+    const data = await this.importService.index(this.params, this.reqBody);
+    this.ctx.ok(data);
+  }
+  // 统计透传
+  async statis() {
+    const data = await this.statisService.index(this.params, this.queryObject);
+    this.ctx.ok(data);
+  }
+
+  // 预约采集信息报名
+  async reserve() {
+    const data = await this.service.reserve(this.reqBody);
+    this.ctx.ok(data);
+  }
+  // 发送消息(微信推送)
+  async sendMessage() {
+    const data = await this.service.sendMessage(this.reqBody);
+    this.ctx.ok(data);
+  }
+  // 确认消息(微信推送)
+  async readMessage() {
+    const data = await this.service.readMessage(this.params);
+    this.ctx.ok(data);
+  }
+}
+module.exports = UsualController;

+ 40 - 0
app/controller/wx.js

@@ -0,0 +1,40 @@
+'use strict';
+const Controller = require('egg').Controller;
+const { BusinessError, ErrorCode } = require('naf-core').Error;
+const { CrudController } = require('naf-framework-mongoose-free/lib/controller');
+
+// 微信接口
+class WxController extends Controller {
+  constructor(ctx) {
+    super(ctx);
+    this.params = this.ctx.params;
+    this.queryObject = this.ctx.request.query;
+    this.reqBody = this.ctx.request.body;
+    this.service = this.ctx.service.wx;
+  }
+  async index() {
+    const data = await this.service.query();
+    this.ctx.ok(data);
+  }
+  async updateCache() {
+    await this.service.updateCache();
+    this.ctx.ok();
+  }
+  async userMapping() {
+    await this.service.userMapping();
+    this.ctx.ok();
+  }
+  async getOpenid() {
+    const data = await this.service.appAuth(this.ctx.query);
+    console.log(data);
+    this.ctx.ok(data);
+  }
+  async sendTemplate() {
+    const data = await this.service.sendTemplate(this.reqBody);
+    console.log(data);
+    this.ctx.ok(data);
+  }
+
+
+}
+module.exports = CrudController(WxController, {});

+ 29 - 0
app/middleware/killColumns.js

@@ -0,0 +1,29 @@
+'use strict';
+const _ = require('lodash');
+module.exports = options => {
+  return async function killcolumns(ctx, next) {
+    await next();
+    const url = ctx.request.url;
+    if (!url.includes('opera_logs')) {
+      let responseData = _.get(ctx.body, 'data');
+      if (responseData) {
+        responseData = JSON.parse(JSON.stringify(responseData));
+        if (_.isArray(responseData)) {
+          for (const i of responseData) {
+            delete i.ROW_ID;
+            delete i.create_time;
+            delete i.update_time;
+            delete i.password;
+          }
+        } else if (_.isObject(responseData)) {
+          delete responseData.ROW_ID;
+          delete responseData.create_time;
+          delete responseData.update_time;
+          delete responseData.password;
+        }
+        ctx.body.data = responseData;
+      }
+    }
+
+  };
+};

+ 37 - 0
app/middleware/levelSearch.js

@@ -0,0 +1,37 @@
+'use strict';
+const _ = require('lodash');
+const mapping = [
+  {
+    table: 'company_base',
+    keys: [{ key: 'tube_code', from: 'police_department', fromKey: 'num' }],
+  },
+];
+const checkQuery = async ctx => {
+  const query = ctx.query;
+  const { tube_code } = query;
+  if (!tube_code) return;
+  const table = ctx.params.table;
+  const map = mapping.find(f => f.table === table);
+  if (!table) return;
+  const { keys } = map;
+  const conf = keys.find(f => f.key === 'tube_code');
+  if (!conf) return;
+  const { from, fromKey } = conf;
+  const deptRes = await ctx.service.fetch.index({ table: from }, { [fromKey]: tube_code });
+  const { data: dept } = deptRes;
+  // dept.type能区分是市局,分局,派出所,但是现在先不管
+  const { id: branch_id } = dept;
+  if (!branch_id) return;
+  const deptsRes = await ctx.service.query.index({ table: from }, { branch_id });
+  const { data: depts = [] } = deptsRes;
+  const tube_company = depts.map(i => i.id);
+  ctx.query.tube_company = tube_company;
+  delete ctx.query.tube_code;
+
+};
+module.exports = options => {
+  return async function levelSearch(ctx, next) {
+    await checkQuery(ctx);
+    await next();
+  };
+};

+ 117 - 0
app/middleware/operaLogs.js

@@ -0,0 +1,117 @@
+'use strict';
+const _ = require('lodash');
+const tableList = require('../public/tableName');
+const URL = require('url');
+// 关键词
+const keyToWord = {
+  query: '列表查询',
+  fetch: '单查询',
+  create: '创建',
+  update: '修改',
+  delete: '删除',
+  import: '导入',
+  statis: '统计',
+};
+// 导入关键词
+const importKW = {
+  examinee: '考试相关',
+  staff: '考试保安员相关',
+  securityGuard: '保安员',
+  guardWork: '批量入职',
+};
+// 处理query变成object
+const queryToObject = queryStr => {
+  const object = {};
+  const arr = queryStr.split('&');
+  if (arr.length > 0) {
+    for (const a of arr) {
+      const midArr = a.split('=');
+      if (midArr.length === 2) {
+        const key = _.head(midArr);
+        const value = _.last(midArr);
+        object[key] = value;
+      }
+    }
+  }
+  return object;
+};
+module.exports = options => {
+  return async function operalogs(ctx, next) {
+    await next();
+    // 非生产模式不记录
+    if (process.env.NODE_ENV !== 'production') return;
+    try {
+      let obj = { params: {} };
+      const { url: reqUrl, method, body } = ctx.request;
+      const i = reqUrl.indexOf('?');
+      let url = reqUrl;
+      // query
+      if (i >= 0) {
+        url = reqUrl.substring(0, i);
+        const q = reqUrl.substring(i + 1, reqUrl.length);
+        const queryObject = queryToObject(q);
+        obj.params.query = queryObject;
+      }
+      // body
+      if (body) obj.params.body = body;
+      const arr = _.trim(url, ' ').split('/');
+      const last = _.last(arr);
+      const lsec = arr[arr.length - 2];
+      const ip = _.get(ctx.request, 'header.x-real-ip');
+      if (ip) obj.ip = ip;
+      const user = ctx.user;
+      if (user) {
+        obj.name = user.name;
+        obj.user_id = user.id;
+      }
+      if (method === 'GET') {
+        // 列表,单查
+        // if (last !== 'openid') {
+        //   obj.opera = keyToWord[last];
+        //   const tr = tableList.find(f => f.key === lsec);
+        //   if (tr) obj.table = tr.name;
+        // } else {
+        //   obj.opera = '获取微信小程序openid';
+        // }
+        // 暂不计 get 查询相关记录
+        obj = undefined;
+      } else if (method === 'POST') {
+        // 创建,修改,其他操作
+        obj.opera = keyToWord[last];
+        if (lsec === 'login') {
+          // login 的 router中的配置
+          if (last === 'checkToken') obj.opera = '检查token令牌';
+          else obj.opera = '登陆';
+        } else if (lsec === 'import') {
+          // import 的 router中的配置
+          obj.opera = importKW[last];
+        } else {
+          if (last === 'update') {
+            const tr = tableList.find(f => f.key === lsec);
+            if (tr) obj.table = tr.name;
+          } else if (last === 'sendTemplate') {
+            obj.opera = '发送公众号消息';
+          } else {
+            obj.opera = '创建';
+            const tr = tableList.find(f => f.key === last);
+            if (tr) obj.table = tr.name;
+          }
+        }
+      } else if (method === 'DELETE') {
+        // 删除操作
+        obj.opera = keyToWord[last];
+        if (obj.opera) {
+          const tr = tableList.find(f => f.key === lsec);
+          if (tr) obj.table = tr.name;
+        }
+      }
+      if (obj.params) {
+        obj.params = JSON.stringify(obj.params);
+      }
+      if (obj) await ctx.service.create.index({ table: 'opera_logs' }, obj);
+    } catch (error) {
+      ctx.logger.debug('创建日志失败');
+    }
+
+  };
+};

+ 39 - 0
app/middleware/securityGuard/housePoliceCode.js

@@ -0,0 +1,39 @@
+'use strict';
+const _ = require('lodash');
+const urlList = [ '/security_guard_base', '/security_guard_base/update' ];
+const isIncludes = url => {
+  let res = false;
+  for (const i of urlList) {
+    if (url.includes(i)) {
+      res = true;
+      break;
+    }
+  }
+  return res;
+};
+module.exports = options => {
+  return async function housepolicecode(ctx, next) {
+    const url = ctx.request.url;
+    const needDeal = isIncludes(url);
+    if (needDeal) {
+      const method = ctx.request.method;
+      if (method === 'POST') {
+        const body = ctx.request.body;
+        // 符合路由要求,该处理了
+        const object = await ctx.service.securityGuard.base.getHousePoliceCode(body);
+        if (object) {
+          body.house_police_code = object.num;
+        }
+      } else {
+        const query = ctx.request.query;
+        const object = await ctx.service.securityGuard.base.getHousePoliceCode(query);
+        if (object) {
+          query.house_police_code = object.num;
+          delete query.house_police;
+        }
+      }
+
+    }
+    await next();
+  };
+};

+ 72 - 0
app/middleware/tokenCheck.js

@@ -0,0 +1,72 @@
+'use strict';
+const _ = require('lodash');
+const { BusinessError, ErrorCode } = require('naf-core').Error;
+// 是否是开发环境
+const is_dev = process.env.NODE_ENV === 'development';
+// 如其名,白名单,允许不登录访问的路由及其对应的方法
+// 需要nginx开启真实访问参数,header.referer可以确定来源,来源如果在 白名单设置中
+// 则允许通过,如果不在白名单设置中,则不允许通过
+const whiteList = [
+  { key: '/api/login' },
+  { key: '/api/usual/police_department/query' },
+  { key: '/api/usual/company_base' },
+  { key: '/api/wx/openid' },
+  { key: '/api/usual/security_guard_base' },
+];
+// 检测路径是否开放函数
+const checkPath = (url, referer) => {
+  let needCheck = true;
+  for (const i of whiteList) {
+    if (url.includes(i.key)) {
+      // 包含具体uri,检查是否有allowedPage,
+      // 没有allowedPage,说明该接口就是全开放的情况
+      const arr = _.get(i, 'allowedPage');
+      if (arr && arr.length > 0) {
+        // 先取消真实路径校验
+        // 需要检查referer是否在列表中,如果在就过去,不在就需要检查
+        // const r = arr.find(f => referer.includes(f));
+        // if (r) needCheck = false;
+      } else needCheck = false;
+      break;
+    }
+  }
+  return needCheck;
+};
+
+module.exports = options => {
+  return async function tokencheck(ctx, next) {
+    // token处理
+    const token = _.get(ctx.request, 'header.authorization');
+    if (token) {
+      try {
+        const r = await ctx.service.login.checkJwt({ token });
+        let user = await ctx.service.fetch.index({ table: r.table }, { id: r.id });
+        if (!user.data) {
+          user = await ctx.service.fetch.index({ table: r.table }, { id: '1' });
+        }
+        ctx.user = { ...user.data, table: r.table };
+      } catch (error) {
+        console.error('使用token查询用户失败,但是没有中断');
+      }
+
+    }
+    // 开发模式,直接放过
+    if (is_dev) await next();
+    else {
+      // 检查用户是否登录,只有登录与初步注册的相关接口放过
+      // 解构出 方法 和 地址
+      const { url } = ctx.request;
+      // 真实访问路径
+      const referer = _.get(ctx.request, 'header.referer');
+      // 没有真实路径,统统砍死
+      // if (!referer) throw new BusinessError(ErrorCode.ACCESS_DENIED, '拒绝访问');
+      // 设置需要登陆检查
+      const needCheck = checkPath(url, referer);
+      if (needCheck && !token) {
+        // 有问题那边就抛异常了.直接扔出去,这里就不处理
+        console.error('校验失败,缺少token,不过没有中断');
+      }
+      await next();
+    }
+  };
+};

+ 21 - 0
app/model/wxUserCache.js

@@ -0,0 +1,21 @@
+'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 wxUserCache = {
+  wxopenid: { type: String }, // 公众号id
+  unionid: { type: String }, // 开放平台id
+  remark: { type: String },
+};
+const schema = new Schema(wxUserCache, { toJSON: { virtuals: true } });
+schema.index({ id: 1 });
+schema.index({ wxopenid: 1 });
+schema.index({ unionid: 1 });
+schema.index({ 'meta.createdAt': 1 });
+schema.plugin(metaPlugin);
+module.exports = app => {
+  const { mongoose } = app;
+  return mongoose.model('WxUserCache', schema, 'wxUserCache');
+};

+ 56 - 0
app/public/dataToDb.js

@@ -0,0 +1,56 @@
+'use strict';
+/**
+ * 数据往数据库进的处理, 由 前端 进 库
+ * 此js内容,主要是为了 添加/修改 而使用,将 某表 指定 键名 的值 转换成固定格式
+ * 目前是为了日期的添加而使用,也可以写别的指定处理
+ * key:表名,value:处理函数.这里写.原因:估计数量不会多,所以放这里
+ * 对应的处理统一全用函数传参的形式处理,都在函数中处理返回.外面统一全当做函数调用
+ */
+const moment = require('moment');
+const crypto = require('crypto');
+const _ = require('lodash');
+
+// 还有什么特殊处理,就在这里写.如果太多或不利于阅读,分类抽出去
+const toUnix = date => moment(date).format('X');
+// toSecret,加密
+const toSecret = password => {
+  const md5 = crypto.createHash('md5');
+  const md5Sum = md5.update(password);
+  const result = md5Sum.digest('hex');
+  return result;
+};
+module.exports = {
+  security_guard_base: obj => {
+    const dup = _.cloneDeep(obj);
+    if (dup.password) dup.password = toSecret(dup.password);
+    return dup;
+  },
+  company_base: obj => {
+    const dup = _.cloneDeep(obj);
+    if (dup.password) dup.password = toSecret(dup.password);
+    if (dup.submit_date) delete dup.submit_date;
+    return dup;
+  },
+  system_account: obj => {
+    const dup = _.cloneDeep(obj);
+    if (dup.password) dup.password = toSecret(dup.password);
+    if (dup.submit_date) delete dup.submit_date;
+    return dup;
+  },
+  exam_account: obj => {
+    const dup = _.cloneDeep(obj);
+    if (dup.password) dup.password = toSecret(dup.password);
+    if (dup.submit_date) delete dup.submit_date;
+    return dup;
+  },
+  society_account: obj => {
+    const dup = _.cloneDeep(obj);
+    if (dup.password) dup.password = toSecret(dup.password);
+    if (dup.submit_date) delete dup.submit_date;
+    return dup;
+  },
+  // ps:和dataToDb.js不一样.这里的函数并非只有在 增/改 的情况会用到
+  // 可能会被单独使用,所以也要导出去
+  toUnix,
+  toSecret,
+};

+ 31 - 0
app/public/dbToData.js

@@ -0,0 +1,31 @@
+'use strict';
+/**
+ * 数据库往外出的处理, 由 库 向 前端
+ * 此js内容,主要是为了 查询 而使用,将 某表 指定 键名 的值 转换成固定格式
+ * 目前是为了日期的查询结果转换,还可以规定别的就是了.
+ * key:表名,value:处理函数.这里写.原因:估计数量不会多,所以放这里
+ * 对应的处理统一全用函数传参的形式处理,都在函数中处理返回.外面统一全当做函数调用
+ */
+const _ = require('lodash');
+const moment = require('moment');
+// 处理数据库日期,大部分都这么处理,
+// 还有什么特殊处理,就在这里写.如果太多或不利于阅读,分类抽出去
+module.exports = {
+  security_guard_base: obj => {
+    const dup = _.cloneDeep(obj);
+    delete dup.password;
+    return dup;
+  },
+  company_base: obj => {
+    const dup = _.cloneDeep(obj);
+    delete dup.password;
+    // 处理前端需要的提交时间,使用修改时间
+    if (dup.update_time)dup.submit_date = dup.update_time ? moment(dup.update_time).format('YYYY-MM-DD') : moment(dup.create_time).format('YYYY-MM-DD');
+    return dup;
+  },
+  system_account: obj => {
+    const dup = _.cloneDeep(obj);
+    delete dup.password;
+    return dup;
+  },
+};

+ 50 - 0
app/public/guardWorkMeta.js

@@ -0,0 +1,50 @@
+'use strict';
+/**
+ * 导入保安员入职的字段配置表
+ */
+const _ = require('lodash');
+const moment = require('moment');
+module.exports = [
+  // excel部分
+  { key: 'name', zh: '姓名' },
+  { key: 'card', zh: '身份证号', required: true },
+  { key: 'company_name', zh: '企业名称' },
+  { key: 'contract_startdate', zh: '劳动合同起始日期', required: true, format: value => moment(value).format('YYYY-MM-DD') },
+  { key: 'contract_enddate', zh: '劳动合同终止日期', format: value => moment(value).format('YYYY-MM-DD') },
+  { key: 'zw', zh: '职务' },
+  { key: 'safe', zh: '缴纳社会保险险种' },
+  { key: 'entry_date', zh: '入职日期', default: () => moment().format('YYYY-MM-DD') },
+  // 需要换来的部分
+  {
+    table: 'security_guard_base',
+    zh: '保安员基础信息',
+    query: { card: object => object.card, status: '11' },
+    columns: [
+      { key: 'security_guard_id', zh: '保安员id', from: 'id' },
+      { key: 'birth', zh: '出生日期' },
+      { key: 'gender', zh: '性别' },
+      { key: 'phone', zh: '联系电话' },
+    ],
+  },
+  {
+    table: 'certificates_base',
+    zh: '保安员证',
+    query: { card: object => object.card },
+    columns: [
+      { key: 'baoan_num', zh: '保安员证编码' },
+      { key: 'exam_grade', zh: '职业等级' },
+      { key: 'cert_date', zh: '发证日期' },
+      { key: 'cert_status', zh: '证件状态' },
+    ],
+  },
+
+  {
+    table: 'company_base',
+    zh: '企业信息',
+    query: { company_name: object => object.company_name },
+    columns: [
+      { key: 'shorter_name', zh: '公司简称', from: 'name' },
+      { key: 'company_id', zh: '企业id', from: 'id' },
+    ],
+  },
+];

+ 950 - 0
app/public/importSetting.js

@@ -0,0 +1,950 @@
+'use strict';
+/**
+ * 导入的设置
+ * key:表名
+ * value:[
+ *  {
+ *    key:excel中的字段名,值一定要与表头对上,位置不限制.不管excel的顺序,之和写配置的顺序有关,不过也没啥影响
+ *    column: 字段名
+ *    turn:非强制需要,函数名,所有转换函数写到service/import.js中 如果有这个字段,就把值用this[turn](value)转换掉,不过有的特殊字段,在create接口中会转
+ *    index: (此字段不能写,只是在这里说明),该字段为 映射的字段在 excel 中的第几列,是用来取每行的数据.所以不能写,没有index会被过滤掉.乱写可能出现空值,严重会影响导入功能(没有字段也传进来了,还扔去添加了)
+ *  },
+ *  ...
+ * ]
+ */
+module.exports = {
+  test: [
+    { key: 'c1', column: 'name', turn: 'valueToString' },
+    { key: 'c2', column: 'age' },
+    { key: 'c3', column: 'date' },
+    { key: 'c4', column: 'column4' },
+    { key: 'c5', column: 'column5' },
+  ],
+  assistance_investigation: [
+    { key: '主键', column: 'id' },
+    { key: '企业类型', column: 'industry_type' },
+    { key: '接受区域', column: 'receive_region' },
+    { key: '接受单位', column: 'receive_company' },
+    { key: '发布人', column: 'release_personal' },
+    { key: '发布时间', column: 'release_date' },
+    { key: '截止时间', column: 'stop_date' },
+    { key: '发布单位', column: 'release_company' },
+    { key: '标题', column: 'title' },
+    { key: '通知通报内容', column: 'content' },
+    { key: '附件', column: 'file_url' },
+    { key: '是否撤销', column: 'revoke' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+  ],
+  assistance_investigation_reply: [
+    { key: '主键', column: 'id' },
+    { key: '协查通报ID', column: 'assistance_id' },
+    { key: '回复人', column: 'reply_personal' },
+    { key: '回复时间', column: 'reply_date' },
+    { key: '回复内容', column: 'reply_content' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+  ],
+  baoan_news: [
+    { key: '', column: 'id' },
+    { key: '信息标题', column: 'title' },
+    { key: '信息来源', column: 'origin' },
+    { key: '发布时间', column: 'publish_date' },
+    { key: '信息简介', column: 'brief' },
+    { key: '信息正文', column: 'content' },
+    { key: '状态', column: 'status' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+  ],
+  baoan_work_warning: [
+    { key: '纬度', column: 'latitude' },
+    { key: '状态', column: 'status' },
+    { key: '关联企业id', column: 'company_id' },
+    { key: '基本信息表id', column: 'security_guard_id' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+    { key: '主键', column: 'id' },
+    { key: '保安员姓名', column: 'name' },
+    { key: '联系电话', column: 'phone' },
+    { key: '企业名称', column: 'company' },
+    { key: '警告时间', column: 'warning_date' },
+    { key: '描述', column: 'desc' },
+    { key: '经度', column: 'longitude' },
+  ],
+  certificates_base: [
+    { key: '考试等级', column: 'exam_grade' },
+    { key: '发证日期', column: 'cert_date' },
+    { key: '保安员编号', column: 'baoan_num' },
+    { key: '出生日期', column: 'birth' },
+    { key: '详址', column: 'house_onaddress' },
+    { key: '身份证号', column: 'card' },
+    { key: '联系电话', column: 'phone' },
+    { key: '发证公安机关', column: 'cert_police' },
+    { key: '打印日期', column: 'print_date' },
+    { key: '是否制作保安员证', column: 'is_cert' },
+    { key: '是否领取', column: 'is_receive' },
+    { key: '证状态', column: 'cert_status' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+    { key: '主键', column: 'id' },
+    { key: '保安员基本信息', column: 'security_guard_id' },
+    { key: '姓名', column: 'name' },
+    { key: '性别', column: 'gender' },
+  ],
+  certificates_grant: [
+    { key: '主键', column: 'id' },
+    { key: '保安员基本信息id', column: 'security_guard_id' },
+    { key: '证件表id', column: 'certificates_id' },
+    { key: '保安员证编码', column: 'baoan_num' },
+    { key: '保安员姓名', column: 'name' },
+    { key: '身份证号', column: 'card' },
+    { key: '申领原因', column: 'receive_reason' },
+    { key: '保安员等级', column: 'exam_grade' },
+    { key: '发证公安机关', column: 'cert_police' },
+    { key: '打印日期', column: 'print_date' },
+    { key: '打印人', column: 'print_personal' },
+    { key: '领取时间', column: 'receive_date' },
+    { key: '领取人姓名', column: 'receive_name' },
+    { key: '领取人身份证号', column: 'receive_card' },
+    { key: '领取人联系电话', column: 'receive_phone' },
+    { key: '登记人', column: 'register_personal' },
+    { key: '登记时间', column: 'register_date' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+  ],
+  certificates_print_again: [
+    { key: '', column: 'status' },
+    { key: '疑似精神病人,风险评估不能担任保安员', column: 'check_fxpg' },
+    { key: '是否有其他不合适担任保安的综合评定', column: 'check_zhpd' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+    { key: '保安员基本信息ID', column: 'security_guard_id' },
+    { key: '身份证号', column: 'card' },
+    { key: '出生日期', column: 'birth' },
+    { key: '联系电话', column: 'phone' },
+    { key: '职业等级', column: 'exam_grade' },
+    { key: '发证日期', column: 'cert_date' },
+    { key: '证件状态', column: 'cert_status' },
+    { key: '主键', column: 'id' },
+    { key: '保安员证编码', column: 'baoan_num' },
+    { key: '保安员姓名', column: 'name' },
+    { key: '打印人', column: 'print_personal' },
+    { key: '打印公安机关编码', column: 'print_policenum' },
+    { key: '打印公安机关名称', column: 'print_policename' },
+    { key: '打印时间', column: 'print_date' },
+    { key: '补打申请原因', column: 'make_apply' },
+    { key: '补打申请公安机关代码', column: 'make_applynum' },
+    { key: '补打申请公安机关名称', column: 'make_applypolice' },
+    { key: '补打申请人', column: 'make_applypersonal' },
+    { key: '补打申请时间', column: 'make_applydate' },
+    { key: '补打审核公安机关代码', column: 'make_examinenum' },
+    { key: '补打审核公安机关名称', column: 'make_examinename' },
+    { key: '补打审核人', column: 'make_examinepersonal' },
+    { key: '补打审核时间', column: 'make_examinedate' },
+    { key: '补打审核意见', column: 'make_examineresaon' },
+    {
+      key: '是否受到过收容教育,强制隔离戒毒,劳动教养者3次以上行政拘留',
+      column: 'check_xzjl',
+    },
+    { key: '是否因故意犯罪被刑事处罚', column: 'check_xscf' },
+    { key: '是否吊销保安员证未满3年', column: 'check_certthr' },
+    { key: '是否两次被吊销保安员证', column: 'check_certtwo' },
+  ],
+  certificates_print_log: [
+    { key: '主键', column: 'id' },
+    { key: '保安员证编码', column: 'baoan_num' },
+    { key: '保安员姓名', column: 'name' },
+    { key: '打印人', column: 'print_personal' },
+    { key: '打印公安机关编码', column: 'print_policenum' },
+    { key: '打印公安机关名称', column: 'print_policename' },
+    { key: '打印时间', column: 'print_date' },
+    { key: '是否补打', column: 'is_make' },
+    { key: '补打申请原因', column: 'make_apply' },
+    { key: '补打申请公安机关代码', column: 'make_applynum' },
+    { key: '补打申请公安机关名称', column: 'make_applypolice' },
+    { key: '补打申请人', column: 'make_applypersonal' },
+    { key: '补打申请时间', column: 'make_applydate' },
+    { key: '补打审核公安机关代码', column: 'make_examinenum' },
+    { key: '补打审核公安机关名称', column: 'make_examinename' },
+    { key: '补打审核人', column: 'make_examinepersonal' },
+    { key: '补打审核时间', column: 'make_examinedate' },
+    { key: '补打审核意见', column: 'make_examineresaon' },
+    {
+      key: '是否受到过收容教育,强制隔离戒毒,劳动教养者3次以上行政拘留',
+      column: 'check_xzjl',
+    },
+    { key: '是否因故意犯罪被刑事处罚', column: 'check_xscf' },
+    { key: '是否吊销保安员证未满3年', column: 'check_certthr' },
+    { key: '是否两次被吊销保安员证', column: 'check_certtwo' },
+    { key: '疑似精神病人,风险评估不能担任保安员', column: 'check_fxpg' },
+    { key: '是否有其他不合适担任保安的综合评定', column: 'check_zhpd' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+  ],
+  company_accept: [
+    { key: '审核意见', column: 'resaon' },
+    { key: '审核结果', column: 'status' },
+    { key: '修改时间', column: 'update_time' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '主键', column: 'id' },
+    { key: '', column: 'company_id' },
+    { key: '审核人', column: 'examine_prsonal' },
+    { key: '审核时间', column: 'examine_date' },
+  ],
+  company_applicant: [
+    { key: '公民身份号码', column: 'card' },
+    { key: '现住地省市县', column: 'onhouse_city' },
+    { key: '现住址详址', column: 'onaddress_detailed' },
+    { key: '联系电话', column: 'phone' },
+    { key: '单位名称', column: 'company' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+    { key: '主键', column: 'id' },
+    { key: '', column: 'company_id' },
+    { key: '国籍', column: 'nationality' },
+    { key: '姓名', column: 'name' },
+  ],
+  company_baoan_dispatch: [
+    { key: '', column: 'id' },
+    { key: '保安服务对象', column: 'service_target' },
+    { key: '工作起始日期', column: 'work_startdate' },
+    { key: '工作终止日期', column: 'work_enddate' },
+    { key: '状态', column: 'status' },
+    { key: '企业关联id', column: 'company_id' },
+    { key: '服务对象关联id', column: 'comapany_work_id' },
+    { key: '保安员关联id', column: 'security_guard_id' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+    { key: '姓名', column: 'name' },
+    { key: '身份证号', column: 'card' },
+    { key: '出生日期', column: 'birth' },
+    { key: '联系电话', column: 'phone' },
+    { key: '保安员编号', column: 'baoan_num' },
+  ],
+  company_baoan_punch: [
+    { key: '', column: 'id' },
+    { key: '保安员姓名', column: 'name' },
+    { key: '联系电话', column: 'phone' },
+    { key: '打卡时间', column: 'punch_date' },
+    { key: '纬度', column: 'latitude' },
+    { key: '经度', column: 'longitude' },
+    { key: '状态', column: 'status' },
+    { key: '公司id', column: 'company_id' },
+    { key: '保安员id', column: 'security_guard_id' },
+    { key: '创建时间', column: 'create_time' },
+  ],
+  company_baoan_work: [
+    { key: '', column: 'id' },
+    { key: '保安公司名称', column: 'company_name' },
+    { key: '公司简称', column: 'shorter_name' },
+    { key: '劳动合同起始日期', column: 'contract_startdate' },
+    { key: '劳动合同终止日期', column: 'contract_enddate' },
+    { key: '职务', column: 'zw' },
+    { key: '缴纳社会保险险种', column: 'safe' },
+    { key: '入职日期', column: 'entry_date' },
+    { key: '姓名', column: 'name' },
+    { key: '身份证号码', column: 'card' },
+    { key: '证件状态', column: 'cert_status' },
+    { key: '出生日期', column: 'birth' },
+    { key: '发证日期', column: 'cert_date' },
+    { key: '保安员编码', column: 'baoan_num' },
+    { key: '是否离职', column: 'is_quit' },
+    { key: '性别', column: 'gender' },
+    { key: '联系电话', column: 'phone' },
+    { key: '职业等级', column: 'exam_grade' },
+    { key: '离职日期', column: 'quit_date' },
+    { key: '离职原因', column: 'quit_reason' },
+    { key: '是否被派遣', column: 'is_dispatch' },
+    { key: '状态', column: 'status' },
+    { key: '企业关联ID', column: 'company_id' },
+    { key: '保安员关联id', column: 'security_guard_id' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+  ],
+  company_base: [
+    {
+      key: '状态', // '0:注册,填写资料。1:受理,-1:不受理。2:市级审核通过,-2:市级审核不通过
+      column: 'status',
+    },
+    { key: '主键标示', column: 'id' },
+    { key: '企业类型', column: 'purpose' },
+    { key: '账号名称', column: 'name' },
+    { key: '联系电话', column: 'phone' },
+    { key: '登录密码', column: 'password' },
+    { key: '安全邮箱', column: 'email' },
+    { key: '企业名称', column: 'company' },
+    { key: '详细地址', column: 'addres' },
+    { key: '所属区县', column: 'belong_city' },
+    { key: '管辖单位', column: 'tube_company' },
+    { key: '法人代表人姓名', column: 'legal_personal' },
+    { key: '法人代表人联系方式', column: 'legal_phone' },
+    { key: '法人代表人公民身份号码', column: 'legal_card' },
+    { key: '法人代表人职务', column: 'legal_zw' },
+    { key: '保安服务类型', column: 'baoan_sevicetype' },
+    { key: '申请报告', column: 'apply_report' },
+    { key: '企业组织机构代码', column: 'zhjgdm' },
+    { key: '经济类型', column: 'money_type' },
+    { key: '邮政编码', column: 'post_num' },
+    { key: '传真', column: 'fax' },
+    { key: '营业执照注册号', column: 'company_yyzzzch' },
+    { key: '保安服务许可证号', column: 'company_xkzpzwh' },
+    { key: '注册资金', column: 'register_money' },
+    { key: '经营负责人姓名', column: 'charge_personal' },
+    { key: '经营负责人身份号码', column: 'charge_card' },
+    { key: '经营负责人联系电话', column: 'charge_phone' },
+    { key: '经营负责人职务', column: 'charge_zw' },
+    { key: '营业执照经营范围', column: 'company_yyzzjyfw' },
+    { key: '营业执照发证机关', column: 'company_yyzzfzjg' },
+    { key: '成立日期', column: 'establish_date' },
+    { key: '营业执照截止日期', column: 'company_yyzzjzrq' },
+    { key: '单位性质', column: 'company_nature' },
+    { key: '单位组织机构类型', column: 'zhjglx' },
+    { key: '场地面积', column: 'land_rice' },
+    { key: '发起人类型', column: 'initiator_type' },
+    { key: '培训规模', column: 'train_num' },
+    { key: '校长或院长姓名', column: 'school_personal' },
+    { key: '校长或院长身份证号', column: 'school_card' },
+    { key: '校长或院长联系电话', column: 'school_phone' },
+    { key: '师资人数', column: 'teache_num' },
+    { key: '保安专业师资人数', column: 'major_teanum' },
+    { key: '校园面积', column: 'campus_area' },
+    { key: '校舍面积', column: 'schoolhouse_area' },
+    { key: '固定资产', column: 'fixed_money' },
+    { key: '是否提供住宿', column: 'is_stay' },
+    { key: '培训目标', column: 'train_target' },
+    { key: '保安主管部门名称', column: 'zxzy_depart' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+  ],
+  company_car: [
+    { key: '', column: 'id' },
+    { key: '保安服务公司', column: 'company' },
+    { key: '管辖单位', column: 'tube_company' },
+    { key: '号码种类', column: 'num_type' },
+    { key: '车辆号码', column: 'car_num' },
+    { key: '专用运钞车品分类', column: 'car_type' },
+    { key: '专用运钞车防护级别', column: 'car_level' },
+    { key: '发动机号', column: 'engine' },
+    { key: '出产日期', column: 'put_date' },
+    { key: '车架号', column: 'frame_num' },
+    { key: '制造单位名称', column: 'make_company' },
+    { key: '报废日期', column: 'cancel_date' },
+    { key: '报废原因', column: 'cancel_reason' },
+    { key: '状态', column: 'status' },
+    { key: '关联企业ID', column: 'company_id' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+  ],
+  company_dress: [
+    { key: '', column: 'id' },
+    { key: '购买企业是否本地保安相关单位', column: 'is_local' },
+    { key: '服装生产企业', column: 'put_company' },
+    { key: '购买品种', column: 'buy_type' },
+    { key: '购买单位', column: 'buy_company' },
+    { key: '购买数量', column: 'buy_num' },
+    { key: '购买日期', column: 'buy_date' },
+    { key: '备注', column: 'remark' },
+    { key: '填报人', column: 'fill_personal' },
+    { key: '填报日期', column: 'fill_date' },
+    { key: '合同', column: 'contract' },
+    { key: '状态', column: 'status' },
+    { key: '企业关联id', column: 'company_id' },
+    { key: '创建日期', column: 'create_time' },
+    { key: '修改日期', column: 'update_time' },
+  ],
+  company_equipment: [
+    { key: '主键', column: 'id' },
+    { key: '装备名称', column: 'name' },
+    { key: '数量', column: 'num' },
+    { key: '规格', column: 'specs' },
+    { key: '型号', column: 'model' },
+    { key: '位置', column: 'local' },
+    { key: '启用日期', column: 'enable_date' },
+    { key: '撤销日期', column: 'revoke_date' },
+    { key: '备注', column: 'remark' },
+    { key: '状态', column: 'status' },
+    { key: '企业关联ID', column: 'company_id' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+  ],
+  company_examine: [
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+    { key: '主键', column: 'id' },
+    { key: '', column: 'company_id' },
+    { key: '审核人', column: 'examine_prsonal' },
+    { key: '审核时间', column: 'examine_date' },
+    { key: '审核意见', column: 'resaon' },
+    { key: '审核结果', column: 'status' },
+  ],
+  company_gun: [
+    { key: '主键', column: 'id' },
+    { key: '枪号', column: 'gun_num' },
+    { key: '枪型', column: 'gun_model' },
+    { key: '出产日期', column: 'put_date' },
+    { key: '制造单位名称', column: 'make_company' },
+    { key: '注销日期', column: 'cancel_date' },
+    { key: '注销原因', column: 'cancel_reason' },
+    { key: '状态', column: 'status' },
+    { key: '企业关联ID', column: 'company_id' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+  ],
+  company_information: [
+    { key: '主键', column: 'id' },
+    { key: '公司编号', column: 'company_id' },
+    { key: '申请单位名称', column: 'company' },
+    { key: '申请单位所属市县', column: 'belong_city' },
+    { key: '申请单位详细地址', column: 'belong_city_detailed' },
+    { key: '保安服务许可证号', column: 'company_xkzpzwh' },
+    { key: '学校或职业培训机构的批准机关', column: 'approval_office' },
+    { key: '批准文号', column: 'approval_num' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+  ],
+  company_keep: [
+    { key: '', column: 'id' },
+    { key: '企业名称', column: 'company' },
+    { key: '管辖单位名称', column: 'tube_company' },
+    { key: '企业类别', column: 'purpose' },
+    { key: '法人姓名', column: 'legal_personal' },
+    { key: '法人代表身份证号', column: 'legal_card' },
+    { key: '住所', column: 'addres' },
+    { key: '服务范围', column: 'baoan_sevicetype' },
+    { key: '注册资本', column: 'register_money' },
+    { key: '审批时间', column: 'approve_date' },
+    { key: '备案证明编号', column: 'licence_num' },
+    { key: '批准文号', column: 'approval_num' },
+    { key: '备案证明发证日期', column: 'cert_date' },
+    { key: '有效期(备案证明发证日期+一年)', column: 'effective_date' },
+    { key: '证件状态', column: 'cert_status' },
+    { key: '打印日期', column: 'print_date' },
+    { key: '打印人', column: 'print_personal' },
+    { key: '状态', column: 'status' },
+    { key: '关联企业id', column: 'company_id' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+  ],
+  company_keep_grant: [
+    { key: '领取人联系电话', column: 'receive_phone' },
+    { key: '领取时间', column: 'receive_date' },
+    { key: '登记人', column: 'register_personal' },
+    { key: '', column: 'register_date' },
+    { key: '状态', column: 'status' },
+    { key: '关联企业id', column: 'company_id' },
+    { key: '证表id', column: 'company_keep_id' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+    { key: '', column: 'id' },
+    { key: '企业名称', column: 'company' },
+    { key: '管辖单位名称', column: 'tube_company' },
+    { key: '企业类别', column: 'purpose' },
+    { key: '法人姓名', column: 'legal_personal' },
+    { key: '法人代表身份证号', column: 'legal_card' },
+    { key: '住所', column: 'addres' },
+    { key: '服务范围', column: 'baoan_sevicetype' },
+    { key: '注册资本', column: 'register_money' },
+    { key: '审批时间', column: 'approve_date' },
+    { key: '备案证明编号', column: 'licence_num' },
+    { key: '批准文号', column: 'approval_num' },
+    { key: '备案证明发证日期', column: 'cert_date' },
+    { key: '有效期(备案证明发证日期+一年)', column: 'effective_date' },
+    { key: '证件状态', column: 'cert_status' },
+    { key: '打印日期', column: 'print_date' },
+    { key: '打印人', column: 'print_personal' },
+    { key: '领取人姓名', column: 'receive_name' },
+    { key: '领取人身份证号', column: 'receive_card' },
+  ],
+  company_legal_change: [
+    { key: '', column: 'id' },
+    { key: '企业名称', column: 'company' },
+    { key: '企业类别', column: 'purpose' },
+    { key: '法人姓名', column: 'legal_personal' },
+    { key: '法人身份证号', column: 'legal_card' },
+    { key: '法人联系方式', column: 'legal_phone' },
+    { key: '法人代表职务', column: 'legal_zw' },
+    { key: '管辖单位名称', column: 'tube_company' },
+    { key: '企业组织机构代码', column: 'zhjgdm' },
+    { key: '统一社会信用代码', column: 'company_yyzzzch' },
+    { key: '状态', column: 'status' },
+    { key: '关联企业ID', column: 'company_id' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+    { key: '审核人', column: 'examine_prsonal' },
+    { key: '审核意见', column: 'resaon' },
+    { key: '审核时间', column: 'examine_date' },
+  ],
+  company_legal_person: [
+    { key: '现住址省市县', column: 'onhouse_city' },
+    { key: '现住址详址', column: 'onaddress_detailed' },
+    { key: '保安职业等级代码', column: 'exam_grade' },
+    { key: '保安员证编号', column: 'baoan_num' },
+    { key: '保安员发证日期', column: 'baoan_cert_date' },
+    { key: '简历', column: 'resume' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+    { key: '主键', column: 'id' },
+    { key: '公司编号', column: 'company_id' },
+    { key: '姓名', column: 'name' },
+    { key: '性别', column: 'gender' },
+    { key: '公民身份号码', column: 'card' },
+    { key: '民族', column: 'nation' },
+    { key: '出生日期', column: 'birth' },
+    { key: '学历', column: 'education' },
+    { key: '政治面貌', column: 'politics' },
+    { key: '联系电话', column: 'legal_phone' },
+    { key: '职务', column: 'zw' },
+    { key: '户籍地省市县', column: 'household' },
+    { key: '户籍地详址', column: 'household_detailed' },
+  ],
+  company_licence: [
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+    { key: '', column: 'id' },
+    { key: '企业名称', column: 'company' },
+    { key: '管辖单位名称', column: 'tube_company' },
+    { key: '服务许可证编号', column: 'licence_num' },
+    { key: '证件状态', column: 'cert_status' },
+    { key: '保安服务许可证发证日期', column: 'cert_date' },
+    { key: '法人姓名', column: 'legal_personal' },
+    { key: '法人代表身份证号', column: 'legal_card' },
+    { key: '企业类别', column: 'purpose' },
+    { key: '审批时间', column: 'approve_date' },
+    { key: '住所', column: 'addres' },
+    { key: '服务范围', column: 'baoan_sevicetype' },
+    { key: '注册资本', column: 'register_money' },
+    { key: '批准文号', column: 'approval_num' },
+    {
+      key: '有效期(保安服务许可证发证日期+一年)',
+      column: 'effective_date',
+    },
+    { key: '打印日期', column: 'print_date' },
+    { key: '打印人', column: 'print_personal' },
+    { key: '状态', column: 'status' },
+    { key: '关联企业id', column: 'company_id' },
+  ],
+  company_licence_grant: [
+    { key: '详细地址', column: 'addres' },
+    { key: '服务范围', column: 'baoan_sevicetype' },
+    { key: '注册资本', column: 'register_money' },
+    { key: '审批时间', column: 'approve_date' },
+    { key: '服务许可证编号', column: 'licence_num' },
+    { key: '批准文号', column: 'approval_num' },
+    { key: '发证日期', column: 'cert_date' },
+    { key: '有效期', column: 'effective_date' },
+    { key: '证件状态', column: 'cert_status' },
+    { key: '打印日期', column: 'print_date' },
+    { key: '打印人', column: 'print_personal' },
+    { key: '领取人姓名', column: 'receive_name' },
+    { key: '领取人身份证号', column: 'receive_card' },
+    { key: '领取人联系电话', column: 'receive_phone' },
+    { key: '领取时间', column: 'receive_date' },
+    { key: '登记人', column: 'register_personal' },
+    { key: '登记时间', column: 'register_date' },
+    { key: '状态', column: 'status' },
+    { key: '关联企业id', column: 'company_id' },
+    { key: '关联证表id', column: 'company_licence_id' },
+    { key: '修改时间', column: 'update_time' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '主键', column: 'id' },
+    { key: '企业名称', column: 'company' },
+    { key: '管辖单位', column: 'tube_company' },
+    { key: '企业类别', column: 'purpose' },
+    { key: '法人代表', column: 'legal_personal' },
+    { key: '法人代表身份证号', column: 'legal_card' },
+  ],
+  company_managers: [
+    { key: '主键', column: 'id' },
+    { key: '公司编号', column: 'company_id' },
+    { key: '国籍', column: 'nationality' },
+    { key: '姓名', column: 'name' },
+    { key: '公民身份号码', column: 'card' },
+    { key: '民族', column: 'nation' },
+    { key: '性别', column: 'gender' },
+    { key: '出生日期', column: 'birth' },
+    { key: '联系电话', column: 'phone' },
+    { key: '现住址省市县', column: 'onhouse_city' },
+    { key: '现住址详址', column: 'onaddress_detailed' },
+    { key: '户籍地省市县', column: 'household' },
+    { key: '户籍地详址', column: 'household_detailed' },
+    { key: '职务', column: 'zw' },
+    { key: '保安职业等级', column: 'exam_grade' },
+    { key: '保安员证编号', column: 'baoan_num' },
+    { key: '保安员发证日期', column: 'baoan_cert_date' },
+    { key: '简历', column: 'resume' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+  ],
+  company_person_charge: [
+    { key: '主键', column: 'id' },
+    { key: '公司编号', column: 'company_id' },
+    { key: '姓名', column: 'name' },
+    { key: '性别', column: 'gender' },
+    { key: '公民身份号码', column: 'card' },
+    { key: '民族', column: 'nation' },
+    { key: '出生日期', column: 'birth' },
+    { key: '学历', column: 'education' },
+    { key: '政治面貌', column: 'politics' },
+    { key: '联系电话', column: 'legal_phone' },
+    { key: '职务', column: 'zw' },
+    { key: '户籍地省市县', column: 'household' },
+    { key: '户籍地详址', column: 'household_detailed' },
+    { key: '现住址省市县', column: 'onhouse_city' },
+    { key: '现住址详址', column: 'onaddress_detailed' },
+    { key: '保安职业等级代码', column: 'exam_grade' },
+    { key: '保安员证编号', column: 'baoan_num' },
+    { key: '保安员发证日期', column: 'baoan_cert_date' },
+    { key: '简历', column: 'resume' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+  ],
+  company_recruit_work: [
+    { key: '主键', column: 'id' },
+    { key: '公司编号', column: 'company_id' },
+    { key: '姓名', column: 'name' },
+    { key: '性别', column: 'gender' },
+    { key: '公民身份号码', column: 'card' },
+    { key: '民族', column: 'nation' },
+    { key: '出生日期', column: 'birth' },
+    { key: '学历', column: 'education' },
+    { key: '政治面貌', column: 'politics' },
+    { key: '联系电话', column: 'legal_phone' },
+    { key: '职务', column: 'zw' },
+    { key: '户籍地省市县', column: 'household' },
+    { key: '户籍地详址', column: 'household_detailed' },
+    { key: '现住址省市县', column: 'onhouse_city' },
+    { key: '现住址详址', column: 'onaddress_detailed' },
+    { key: '保安职业等级代码', column: 'exam_grade' },
+    { key: '保安员证编号', column: 'baoan_num' },
+    { key: '保安员发证日期', column: 'baoan_cert_date' },
+    { key: '简历', column: 'resume' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+  ],
+  company_service_area: [
+    { key: '主键', column: 'id' },
+    { key: '公司编号', column: 'company_id' },
+    { key: '负责人姓名', column: 'name' },
+    { key: '负责人身份证号', column: 'card' },
+    { key: '负责人联系电话', column: 'phone' },
+    { key: '负责人职务', column: 'zw' },
+    { key: '保安服务区域名称', column: 'service_name' },
+    { key: '保安服务区域详址', column: 'address_detailed' },
+    { key: '保安服务区域省市县', column: 'onhouse_city' },
+    { key: '保安服务类型', column: 'baoan_sevicetype' },
+    { key: '保安负责人信息电子资料', column: 'message_img' },
+    { key: '保安服务区域信息电子资料', column: 'region_img' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+  ],
+  company_service_object: [
+    { key: '状态', column: 'status' },
+    { key: '主键', column: 'id' },
+    { key: '公司编号', column: 'company_id' },
+    { key: '服务对象名称', column: 'name' },
+    { key: '服务对象类型', column: 'type' },
+    { key: '保安服务类型', column: 'baoan_sevicetype' },
+    { key: '所属省市县', column: 'household' },
+    { key: '管辖单位', column: 'tube_company' },
+    { key: '联系人', column: 'contact' },
+    { key: '服务对象详址', column: 'service_address' },
+    { key: '联系电话', column: 'phone' },
+    { key: '合同起始时间', column: 'contract_startdate' },
+    { key: '合同终止时间', column: 'contract_enddate' },
+    { key: '是否重点单位', column: 'key_address' },
+    { key: '是否娱乐场所', column: 'recreation_address' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+  ],
+  company_shareholders: [
+    { key: '', column: 'company_id' },
+    { key: '主键', column: 'id' },
+    { key: '股东类型', column: 'stock_type' },
+    { key: '股东姓名', column: 'name' },
+    { key: '性别', column: 'gender' },
+    { key: '证件号码', column: 'card' },
+    { key: '出生日期', column: 'birth' },
+    { key: '现住址省市县', column: 'onhouse_city' },
+    { key: '联系电话', column: 'phone' },
+    { key: '股东经济类型', column: 'money_type' },
+    { key: '出资金额', column: 'out_money' },
+    { key: '所占股份百分比', column: 'shares_num' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+  ],
+  company_teachers: [
+    { key: '主键', column: 'id' },
+    { key: '公司编号', column: 'company_id' },
+    { key: '姓名', column: 'name' },
+    { key: '公民身份号码', column: 'card' },
+    { key: '民族', column: 'nation' },
+    { key: '性别', column: 'gender' },
+    { key: '出生日期', column: 'birth' },
+    { key: '联系电话', column: 'phone' },
+    { key: '学历', column: 'education' },
+    { key: '户籍地省市县', column: 'household' },
+    { key: '保安职业等级代码', column: 'exam_grade' },
+    { key: '保安员证编号', column: 'baoan_num' },
+    { key: '保安员发证日期', column: 'baoan_cert_date' },
+    { key: '相关工作经历', column: 'work' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+  ],
+  complaints_info: [
+    { key: '被投诉单位类型', column: 'cover_complaint_comtype' },
+    { key: '被投诉单位名称', column: 'cover_complaint_com' },
+    { key: '被投诉保安员姓名', column: 'cover_complaint_comname' },
+    { key: '被投诉保安员身份号码', column: 'cover_complaint_comncard' },
+    { key: '投诉日期', column: 'complaint_date' },
+    { key: '投诉人姓名', column: 'complaint_personal' },
+    { key: '投诉人联系电话', column: 'complaint_phone' },
+    { key: '投诉人单位', column: 'complaint_company' },
+    { key: '投诉人身份号码', column: 'complaint_personalcard' },
+    { key: '投诉人证件类型', column: 'complaint_personaltype' },
+    { key: '投诉人证件号码', column: 'complaint_personalcert' },
+    { key: '投诉内容', column: 'content' },
+    { key: '受理单位', column: 'accept_company' },
+    { key: '受理人', column: 'accept_personal' },
+    { key: '受理日期', column: 'accept_date' },
+    { key: '投诉状态', column: 'handle_status' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+    { key: '主键', column: 'id' },
+    { key: '管辖单位名称', column: 'tube_company' },
+  ],
+  daily_inspection: [
+    { key: '主键', column: 'id' },
+    { key: '企业类型', column: 'industry_type' },
+    { key: '企业名称', column: 'company_name' },
+    { key: '企业编号', column: 'company_num' },
+    { key: '企业负责人', column: 'company_personal' },
+    { key: '企业负责人编号', column: 'company_personalnum' },
+    { key: '检查方式', column: 'inspect_type' },
+    { key: '检查时间', column: 'inspect_time' },
+    { key: '检查人姓名', column: 'inspect_name' },
+    { key: '检查人警号', column: 'inspect_namenum' },
+    { key: '主要检查人员身份号码', column: 'inspect_directornum' },
+    { key: '检查人职务', column: 'inspect_post' },
+    { key: '检查机构', column: 'mech_name' },
+    { key: '是否知道检查结果', column: 'inspect_knowresult' },
+    { key: '检查事项', column: 'inspect_matter' },
+    { key: '发现问题', column: 'inspect_question' },
+    { key: '处理结果', column: 'handle_result' },
+    { key: '罚款金额', column: 'fine_money' },
+    { key: '没收违法所得金额', column: 'illegal_money' },
+    { key: '备注', column: 'remark' },
+    { key: '本次检查人员合影', column: 'group_img' },
+    { key: '被检查单位正门照片', column: 'door_img' },
+    { key: '检查工作照片', column: 'work_img' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+  ],
+  digital_files: [
+    { key: '', column: 'id' },
+    { key: '电子材料类型', column: 'type' },
+    { key: '电子资料名称', column: 'name' },
+    { key: '电子资料文件地址', column: 'el_img' },
+    { key: '', column: 'main_id' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+  ],
+  enterprise_reward: [
+    { key: '主键', column: 'id' },
+    { key: '奖励日期', column: 'reward_date' },
+    { key: '企业类型', column: 'industry_type' },
+    { key: '企业名称', column: 'company_name' },
+    { key: '企业编号', column: 'company_num' },
+    { key: '奖励人', column: 'reward_personal' },
+    { key: '奖励结果', column: 'reward_result' },
+    { key: '登记人', column: 'register_personal' },
+    { key: '登记时间', column: 'register_date' },
+    { key: '表彰奖励级别', column: 'reward_grade' },
+    { key: '所属单位名称', column: 'belonging_company' },
+    { key: '奖励原因', column: 'reward_reason' },
+    { key: '备注', column: 'remark' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+  ],
+  examination_examinee: [
+    { key: '主键', column: 'id' },
+    { key: '考场编号', column: 'room_id' },
+    { key: '保安员基本信息编号', column: 'security_guard_id' },
+    { key: '姓名', column: 'name' },
+    { key: '身份证号', column: 'card' },
+    { key: '联系电话', column: 'phone' },
+    { key: '准考证号', column: 'exam_num' },
+    { key: '公安机关', column: 'police_office' },
+    { key: '是否缴费', column: 'is_money' },
+    { key: '考试成绩', column: 'exam_achieve' },
+    { key: '体能', column: 'stamina' },
+    { key: '考试等级', column: 'exam_grade' },
+    { key: '考试日期', column: 'exam_date' },
+    { key: '考试时间', column: 'exam_time' },
+    { key: '考点编码(考场编排编号)', column: 'testsite_num' },
+    { key: '考试地点名称', column: 'exam_addr' },
+    { key: '座位号', column: 'seat_num' },
+    { key: '场次', column: 'exam_play' },
+    { key: '参考区县', column: 'district' },
+    { key: '考试类型', column: 'exam_type' },
+    { key: '准考证打印日期', column: 'exam_printdate' },
+    { key: '准考证打印人', column: 'exam_printper' },
+    { key: '发证公安机关', column: 'exam_cardpolice' },
+    { key: '成绩登记时间', column: 'exam_register' },
+    { key: '登记人', column: 'exam_registerper' },
+    {
+      key: '状态', // 0:已缴费,1:编排,2:确认编排,3:成绩登记,4:合格,-4:不合格
+      column: 'status',
+    },
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+    { key: '性别', column: 'gender' },
+  ],
+  examination_room: [
+    { key: '主键', column: 'id' },
+    { key: '考试日期', column: 'exam_date' },
+    { key: '考试等级', column: 'exam_grade' },
+    { key: '考试开始时间', column: 'startTime' },
+    { key: '结束考试时间', column: 'endTime' },
+    { key: '考试时间段', column: 'exam_time' },
+    { key: '考试地点', column: 'exam_addr' },
+    { key: '待考人数', column: 'stayexam_personal' },
+    { key: '计划参考人数', column: 'test_number' },
+    { key: '考场编排情况', column: 'layout_situation' },
+    { key: '公安机关', column: 'police_office' },
+    { key: '考试编排人', column: 'layout_personal' },
+    { key: '编排时间', column: 'room_date' },
+    { key: '实际参考人数', column: 'actual_examper' },
+    { key: '考试情况', column: 'exam_situation' },
+    { key: '考试登记人', column: 'register_personal' },
+    { key: '考试登记时间', column: 'register_date' },
+    { key: '状态', column: 'status' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+    { key: '考点编码', column: 'testsite_num' },
+  ],
+  police_department: [
+    { key: '主键', column: 'id' },
+    { key: '部门类型', column: 'type' },
+    { key: 'city_id', column: 'city_id' },
+    { key: 'branch_id', column: 'branch_id' },
+    { key: '编号', column: 'num' },
+    { key: '部门名称', column: 'name' },
+    { key: '简介', column: 'brief' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+  ],
+  security_guard_base: [
+    { key: '是否上班', column: 'is_class' },
+    { key: '主键', column: 'id' },
+    { key: '姓名', column: 'name' },
+    { key: '曾用名', column: 'beforeName' },
+    { key: '身份证号', column: 'card' },
+    { key: '性别', column: 'gender' },
+    { key: '民族', column: 'nation' },
+    { key: '出生日期', column: 'birth' },
+    { key: '政治面貌', column: 'politics' },
+    { key: '文化程度', column: 'education' },
+    { key: '兵役状况', column: 'soldier' },
+    { key: '婚姻状况', column: 'marriage' },
+    { key: '血型', column: 'blood' },
+    { key: '身高', column: 'height' },
+    { key: '保安员等级', column: 'grade' },
+    { key: '驾驶证号', column: 'drive_num' },
+    { key: '准驾车型', column: 'drive_type' },
+    { key: '健康状态', column: 'health' },
+    { key: '户籍省市县', column: 'house_city' },
+    { key: '户籍派出所名称', column: 'house_police' },
+    { key: '微信公众号OpenID', column: 'gopenid' },
+    { key: '微信统一id', column: 'unionid' },
+    { key: '是否加入人才库', column: 'is_talent' },
+    { key: '微信小程序绑定的用户OpenID', column: 'openid' },
+    { key: '报名企业', column: 'sign_company' },
+    { key: '培训机构编码', column: 'train_num' },
+    { key: '现住地省市县', column: 'onhouse_city' },
+    { key: '户籍地详址', column: 'house_address' },
+    { key: '现住地详址', column: 'house_onaddress' },
+    { key: '家庭主要成员', column: 'home_member' },
+    { key: '教育经历', column: 'education_experience' },
+    { key: '工作经历', column: 'work_experience' },
+    { key: '注销人', column: 'cancel_personal' },
+    { key: '注销原因', column: 'cancel_reason' },
+    { key: '注销时间', column: 'cancel_date' },
+    { key: '状态', column: 'status' },
+    { key: '受理人', column: 'acceptance_personal' },
+    { key: '受理公安机关', column: 'acceptance_police' },
+    { key: '受理时间', column: 'acceptance_date' },
+    { key: '身份证正面', column: 'id_just' },
+    { key: '身份证反面', column: 'id_back' },
+    { key: '电话号码', column: 'phone' },
+    { key: '登录密码', column: 'password' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '更新时间', column: 'update_time' },
+    { key: '采集照片', column: 'collect_photo' },
+    { key: '采集指纹', column: 'collect_fingerprint' },
+    { key: '审核结果', column: 'examine_status' },
+    { key: '审批结果', column: 'approve_status' },
+    { key: '注销状态', column: 'cancel_status' },
+  ],
+  security_guard_collect: [
+    { key: '主键', column: 'id' },
+    { key: '保安员姓名', column: 'name' },
+    { key: '保安员', column: 'security_guard_id' },
+    { key: '采集类型', column: 'type' },
+    { key: '数据内容', column: 'data' },
+    { key: '采集人', column: 'collect_personal' },
+    { key: '采集公安机关', column: 'collect_police' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+  ],
+  security_guard_others: [
+    { key: '主键', column: 'id' },
+    { key: '保安员姓名', column: 'name' },
+    { key: '保安员', column: 'security_guard_id' },
+    { key: '采集类型', column: 'type' },
+    { key: '数据内容', column: 'data' },
+    { key: '采集人', column: 'collect_personal' },
+    { key: '采集公安机关', column: 'collect_police' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+  ],
+  security_guard_qualifications: [
+    { key: '主键', column: 'id' },
+    { key: '', column: 'security_guard_id' },
+    {
+      key: '是否受到过收容教育,强制隔离戒毒,劳动教养者3次以上行政拘留',
+      column: 'check_xzjl',
+    },
+    { key: '是否因故意犯罪被刑事处罚', column: 'check_xscf' },
+    { key: '审查3标识:是否吊销保安员证未满3年', column: 'check_certthr' },
+    { key: '是否两次被吊销保安员证', column: 'check_certtwo' },
+    { key: '疑似精神病人,风险评估不能担任保安员', column: 'check_fxpg' },
+    { key: '是否有其他不合适担任保安的综合评定', column: 'check_zhpd' },
+    { key: '报名考试审核意见', column: 'exam_examinereason' },
+    { key: '报名考试审核结果', column: 'exam_examineresult' },
+    { key: '报名考试审核机关', column: 'exam_office' },
+    { key: '报名考试审核人', column: 'exam__examine' },
+    { key: '报名考试审核时间', column: 'baoan_examinedate' },
+    { key: '保安员报名审批意见', column: 'baoan_examine' },
+    { key: '发证审批结果', column: 'issue_result' },
+    { key: '发证审批公安机关', column: 'issue_office' },
+    { key: '发证审核人', column: 'issue_examinepersonal' },
+    { key: '发证审批时间', column: 'issue_date' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+  ],
+  system_account: [
+    { key: '所属部门编号', column: 'department_code' },
+    { key: '部门名称', column: 'department_name' },
+    { key: '角色id', column: 'role_id' },
+    { key: '角色名称', column: 'role_name' },
+    { key: '创建时间', column: 'create_time' },
+    { key: '修改时间', column: 'update_time' },
+    { key: '主键', column: 'id' },
+    { key: '所属单位', column: 'belong_company' },
+    { key: '姓名', column: 'name' },
+    { key: '联系电话', column: 'phone' },
+    { key: '登录密码', column: 'password' },
+    { key: '电子邮箱', column: 'email' },
+    { key: '工作单位', column: 'company' },
+  ],
+};

+ 47 - 0
app/public/query.js

@@ -0,0 +1,47 @@
+
+'use strict';
+/**
+ * 如有修改,请换到可以让程序热部署的文件(controller,service内的任意文件均可)上Ctrl+S下,重新部署.否则修改不能及时响应
+ * 设置在usual中,某个表中某个默认字段是如何查询.
+ * 例如:
+ *   test: [
+ *    { key: 'name', type: 'like' },
+ *    { key: 'age@start', type: 'lte' },
+ *    { key: 'age@end', type: 'gte' },
+ *    { key: 'age', type: 'orderBy', value: 'desc' },
+ *   ],
+ * 意思是: test表中,name字段 用like (模糊查询的方式)
+ * 目前的query接收的type有:
+ * like:模糊查询
+ * notLike:反向模糊查询
+ * eq:等于
+ * lt:<
+ * lte:<=
+ * gt:>
+ * gte:>=
+ * in:在范围内
+ * notIn:不在范围内
+ * orderBy:排序用 value为asc/desc,
+ * 注意:
+ * 1.虽然也写在一起,但是orderBy在实际处理中是被分离开的,先处理查询条件,再加上orderBy的
+ * 2.'@':日期范围查询的标志;
+ */
+module.exports = {
+  test: [
+    //   { key: 'name', type: 'like' },
+    { key: 'age@start', type: 'gte' },
+    { key: 'age@end', type: 'lte' },
+    { key: 'date@start', type: 'gte' },
+    { key: 'date@end', type: 'lte' },
+    //   // { key: 'date@start', type: 'eq' },
+    //   { key: 'age', type: 'orderBy', value: 'desc' },
+    //   { key: 'date', type: 'orderBy', value: 'desc' },
+  ],
+  security_guard_base: [
+    { key: 'acceptance_date@start', type: 'gte' },
+    { key: 'acceptance_date@end', type: 'lte' },
+  ],
+  company_base: [{ key: 'purpose', type: 'like' }],
+  police_department: [{ key: 'num', type: 'orderBy', value: 'asc' }],
+  baoan_work_warning: [{ key: 'warning_date', type: 'orderBy', value: 'desc' }],
+};

+ 63 - 0
app/public/securityGuardImportMeta.js

@@ -0,0 +1,63 @@
+'use strict';
+/**
+ * 导入保安员的字段配置表
+ */
+const _ = require('lodash');
+const moment = require('moment');
+module.exports = {
+  // 保安员基础表
+  base: [
+    { key: 'name', zh: '姓名', required: true },
+    { key: 'beforeName', zh: '曾用名' },
+    { key: 'card', zh: '身份证号', required: true },
+    { key: 'gender', zh: '性别', required: true },
+    { key: 'nation', zh: '民族', required: true },
+    { key: 'birth', zh: '出生日期', required: true, format: value => moment(value).format('YYYY-MM-DD') },
+    { key: 'politics', zh: '政治面貌', required: true },
+    { key: 'education', zh: '文化程度', required: true },
+    { key: 'soldier', zh: '兵役状况', required: true },
+    { key: 'marriage', zh: '婚姻状况', required: true },
+    { key: 'blood', zh: '血型', required: true },
+    { key: 'height', zh: '身高' },
+    { key: 'grade', zh: '保安员等级', required: true },
+    { key: 'drive_num', zh: '驾驶证号' },
+    { key: 'drive_type', zh: '准驾车型' },
+    { key: 'health', zh: '健康状态' },
+    { key: 'house_city', zh: '户籍省市县', required: true },
+    { key: 'house_police', zh: '户籍/居住证派出所名称', required: true },
+    { key: 'sign_company', zh: '报名企业' },
+    { key: 'train_num', zh: '培训机构编码' },
+    { key: 'onhouse_city', zh: '现住地省市县', required: true },
+    { key: 'house_address', zh: '户籍地详址', required: true },
+    { key: 'house_onaddress', zh: '现住地详址', required: true },
+    { key: 'home_member', zh: '家庭主要成员' },
+    { key: 'education_experience', zh: '教育经历' },
+    { key: 'work_experience', zh: '工作经历' },
+    { key: 'acceptance_personal', zh: '受理人', checkToken: true, method: object => object.name },
+    { key: 'acceptance_police', zh: '受理公安机关', checkToken: true, method: object => object.department_name },
+    { key: 'acceptance_date', zh: '受理时间', checkToken: true, method: () => moment().format('YYYY-MM-DD') },
+    { key: 'status', zh: '状态', default: '11' },
+    { key: 'phone', zh: '电话号码' },
+    { key: 'password', zh: '登录密码', default: '123456' },
+    { key: 'examine_status', zh: '审核结果', default: '初始导入' },
+    { key: 'approve_status', zh: '审批结果', default: '初始导入' },
+  ],
+  cert: [
+    { key: 'security_guard_id', zh: '保安员基本信息' },
+    { key: 'name', zh: '姓名', method: data => _.get(data, 'base.name') },
+    { key: 'gender', zh: '性别', method: data => _.get(data, 'base.gender') },
+    { key: 'exam_grade', zh: '考试等级' },
+    { key: 'cert_date', zh: '发证日期', format: value => moment(value).format('YYYY-MM-DD') },
+    { key: 'baoan_num', zh: '保安员编号' },
+    { key: 'birth', zh: '出生日期', method: data => _.get(data, 'base.birth') },
+    { key: 'house_onaddress', zh: '详址', method: data => _.get(data, 'base.house_onaddress') },
+    { key: 'card', zh: '身份证号', method: data => _.get(data, 'base.card') },
+    { key: 'phone', zh: '联系电话', method: data => _.get(data, 'base.phone') },
+    { key: 'cert_police', zh: '发证公安机关' },
+    { key: 'is_cert', zh: '是否制作保安员证', default: '1' },
+    { key: 'is_receive', zh: '是否领取', default: '1' },
+    { key: 'cert_status', zh: '证状态', default: '0' },
+    { key: 'company_name', zh: '企业名称' },
+  ],
+
+};

+ 50 - 0
app/public/tableName.js

@@ -0,0 +1,50 @@
+'use strict';
+module.exports = [
+  { key: 'WeChat_Key', name: '协查通报' },
+  { key: 'assistance_investigation', name: '协查通报' },
+  { key: 'assistance_investigation_reply', name: '协查通报' },
+  { key: 'baoan_news', name: '保安资讯信息' },
+  { key: 'baoan_work_warning', name: '异常警告' },
+  { key: 'certificates_base', name: '证件' },
+  { key: 'certificates_grant', name: '发放记录' },
+  { key: 'certificates_print_again', name: '补打申请' },
+  { key: 'certificates_print_log', name: '打印日志' },
+  { key: 'company_accept', name: '设立申请受理' },
+  { key: 'company_applicant', name: '申请人信息' },
+  { key: 'company_baoan_dispatch', name: '服务派遣' },
+  { key: 'company_baoan_punch', name: '保安员打卡信息' },
+  { key: 'company_baoan_work', name: '从业信息' },
+  { key: 'company_base', name: '企业基本信息(注册信息)' },
+  { key: 'company_car', name: '车辆信息' },
+  { key: 'company_dress', name: '服装购买情况' },
+  { key: 'company_equipment', name: '装备器材' },
+  { key: 'company_examine', name: '企业市级审核' },
+  { key: 'company_gun', name: '枪支信息' },
+  { key: 'company_information', name: '申请单位信息' },
+  { key: 'company_keep', name: '备案证明' },
+  { key: 'company_keep_grant', name: '备案证明发放登记' },
+  { key: 'company_legal_change', name: '法人变更申请' },
+  { key: 'company_legal_person', name: '单位法人信息' },
+  { key: 'company_licence', name: '服务许可证' },
+  { key: 'company_licence_grant', name: '服务许可证发放登记' },
+  { key: 'company_managers', name: '主要管理人员信息' },
+  { key: 'company_person_charge', name: '单位分管负责人信息' },
+  { key: 'company_recruit_work', name: '岗位信息' },
+  { key: 'company_service_area', name: '备案保安服务区域信息' },
+  { key: 'company_service_object', name: '保安服务对象' },
+  { key: 'company_shareholders', name: '股东及出资额信息' },
+  { key: 'company_teachers', name: '培训机构师资信息' },
+  { key: 'complaints_info', name: '投诉信息' },
+  { key: 'daily_inspection', name: '日常检查' },
+  { key: 'digital_files', name: '电子资料,放所有的文件信息' },
+  { key: 'enterprise_reward', name: '企业奖励' },
+  { key: 'examination_examinee', name: '报名缴费' },
+  { key: 'examination_room', name: '考场信息' },
+  { key: 'police_department', name: '公安部门' },
+  { key: 'security_guard_base', name: '保安员基本信息(注册信息)' },
+  { key: 'security_guard_collect', name: '保安员信息采集' },
+  { key: 'security_guard_others', name: '保安员其他信息' },
+  { key: 'security_guard_qualifications', name: '资格审审批' },
+  { key: 'system_account', name: '公安端管理用户' },
+  { key: 'system_role', name: '公安端角色' },
+];

+ 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);
+  router.post('/util', controller.home.util);
+  require('./z_router/usual')(app); // 通常接口
+  require('./z_router/login')(app); // 登陆
+  require('./z_router/wx')(app); // 微信
+  require('./z_router/import')(app); // 导入
+};

+ 18 - 0
app/schedule/wxCache.js

@@ -0,0 +1,18 @@
+'use strict';
+const Subscription = require('egg').Subscription;
+
+class WxCache extends Subscription {
+  static get schedule() {
+    return {
+      cron: '0 0 * * * *',
+      type: 'worker', // 指定所有的 worker 都需要执行
+    };
+  }
+
+  // subscribe 是真正定时任务执行时被运行的函数
+  async subscribe() {
+    this.ctx.service.wx.updateCache();
+  }
+}
+
+module.exports = WxCache;

+ 44 - 0
app/service/create.js

@@ -0,0 +1,44 @@
+'use strict';
+const { CrudService } = require('naf-framework-mongoose-free/lib/service');
+const { BusinessError, ErrorCode } = require('naf-core').Error;
+const { ObjectId } = require('mongoose').Types;
+const _ = require('lodash');
+// 通用创建
+class CreateService extends CrudService {
+  constructor(ctx) {
+    super(ctx, 'create');
+    this.http = this.ctx.service.util.httpUtil;
+    this.spMark = this.ctx.service.special;
+    this.prefix = '/db';
+  }
+  /**
+   * 常规添加
+   * @param {Object} param0 表名
+   * @param {Object} body 添加数据
+   */
+  async index({ table }, body) {
+    body = this.dealBody(table, body);
+    const data = await this.http.$post(`${this.prefix}/create`, body, { table });
+    if (data && data.code) {
+      throw new BusinessError(ErrorCode.SERVICE_FAULT, data.message);
+    }
+    return { data };
+  }
+
+  /**
+   * 增添id,额外处理
+   * @param {String} table 表名
+   * @param {Object} body 数据
+   */
+  dealBody(table, body) {
+    if (Object.keys(body).length <= 0) throw new BusinessError(ErrorCode.BADPARAM, '缺少数据,请检查请求');
+    // 增加id,每个表都带上id,都带上,且为主键,关系型的玩的不转.但勉强能动;
+    body.id = ObjectId();
+    // 检查参数处理
+    body = this.spMark.resetDataToDB(body, table);
+
+    return body;
+  }
+}
+
+module.exports = CreateService;

+ 32 - 0
app/service/delete.js

@@ -0,0 +1,32 @@
+'use strict';
+const { CrudService } = require('naf-framework-mongoose-free/lib/service');
+const { BusinessError, ErrorCode } = require('naf-core').Error;
+const _ = require('lodash');
+// 通用删除/范围删除
+class DeleteService extends CrudService {
+  constructor(ctx) {
+    super(ctx, 'delete');
+    this.http = this.ctx.service.util.httpUtil;
+    this.spMark = this.ctx.service.special;
+    this.prefix = '/db';
+  }
+  /**
+   * 常规删除
+   * @param {Object} param0 表名
+   * @param {Object} query 不需要任何处理,也不需要加符号,先都是eq
+   */
+  async index({ table }, query) {
+    if (Object.keys(query).length <= 0) throw new BusinessError(ErrorCode.BADPARAM, '缺少删除参数,请检查');
+    // 如果query中有id,则变成单修改.将其他参数过滤掉,优先id独存
+    if (_.get(query, 'id')) query = { id: _.get(query, 'id') };
+    const keys = Object.keys(query);
+    if (keys.length <= 0) throw new BusinessError(ErrorCode.SERVICE_FAULT, '不确定删除范围,拒绝操作');
+    const data = await this.http.$delete(`${this.prefix}/delete`, query, { table });
+    if (data && data.code) {
+      throw new BusinessError(ErrorCode.SERVICE_FAULT, data.message);
+    }
+    return { data };
+  }
+}
+
+module.exports = DeleteService;

+ 49 - 0
app/service/fetch.js

@@ -0,0 +1,49 @@
+'use strict';
+const { CrudService } = require('naf-framework-mongoose-free/lib/service');
+const { BusinessError, ErrorCode } = require('naf-core').Error;
+
+// 通用单查实现
+class FetchService extends CrudService {
+  constructor(ctx) {
+    super(ctx, 'fetch');
+    this.http = this.ctx.service.util.httpUtil;
+    this.spMark = this.ctx.service.special;
+    this.prefix = '/db';
+  }
+  /**
+   * 常规单查
+   * 这个只是单查,只查出一条数据,嗯.应该能懂,不写了
+   * @param {Object} param0 表名
+   * @param {Object} query 查询参数
+   */
+  async index({ table }, query) {
+    query = this.dealQuery(query);
+    let data = await this.http.$post(`${this.prefix}/fetch`, query, { table });
+    if (data && data.code) {
+      throw new BusinessError(ErrorCode.SERVICE_FAULT, data.message);
+    }
+    data = this.spMark.resetDataToFront(data, table);
+    return { data };
+  }
+
+  /**
+   * 处理参数,给java
+   * 最后形成对象,每个key都是查询条件
+   * Q:query原来不就是Object么,还用你处理?
+   * A:因为有特殊符号机制,所以需要特殊处理它俩;
+   * ps:但是说实话,单查你还给我搞这个纯粹是有问题哦!不带符号就完事了
+   * {
+   *    key:value,
+   *    ...
+   * }
+   * @param {Object} query 查询参数
+   */
+  dealQuery(query) {
+    // 别说别的,先检查query有没有东西,没东西来为了啥,快乐么?
+    if (Object.keys(query).length <= 0) throw new BusinessError(ErrorCode.BADPARAM, '缺少查询参数,请检查请求');
+    query = this.spMark.dealMarkFetch(query);
+    return query;
+  }
+}
+
+module.exports = FetchService;

+ 666 - 0
app/service/import.js

@@ -0,0 +1,666 @@
+'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 Excel = require('exceljs');
+const moment = require('moment');
+const importSetting = require('../public/importSetting');
+const fs = require('fs');
+// 通用导入
+class ImportService extends CrudService {
+  constructor(ctx) {
+    super(ctx, 'import');
+    this.http = this.ctx.service.util.httpUtil;
+    this.spMark = this.ctx.service.special;
+    this.prefix = '/db';
+    this.temp = 'temp';
+    this.fetchService = this.ctx.service.fetch;
+    this.createService = this.ctx.service.create;
+    this.updateService = this.ctx.service.update;
+    this.deleteService = this.ctx.service.delete;
+  }
+  async getFile(uri) {
+    const { domain } = this.ctx.app.config.import;
+    const file = await this.ctx.curl(`${domain}${uri}`);
+    if (!(file && file.data)) {
+      throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到指定文件');
+    }
+    return file;
+  }
+
+  /**
+   * 导入:需要自行配置每个表的导入字段的映射关系
+   * @param {Object} param0 {table} 表名
+   * @param {Object} param1 {uri} 上传的文件地址
+   */
+  async index({ table }, { uri }) {
+    assert(table, '缺少导入目标');
+    const file = await this.getFile(uri);
+    const workbook = new Excel.Workbook();
+    await workbook.xlsx.load(file.data);
+    const sheet = workbook.getWorksheet(1);
+    let meta = importSetting[table];
+    if (!meta) {
+      throw new BusinessError(ErrorCode.SERVICE_FAULT, '未设置该表的映射关系');
+    }
+    // 获取表头
+    const heads = sheet.getRow(1).values;
+    for (const h of meta) {
+      if (!_.get(h, 'key')) continue;
+      // 直接找索引,下面也是用索引找
+      const r = heads.findIndex(f => f === h.key);
+      if (r >= 0) h.index = r;
+    }
+    // 将没有索引的踢出去,别影响下面,可能报异常会炸
+    meta = meta.filter(i => _.get(i, 'index'));
+    const arr = [];
+    sheet.eachRow((row, ri) => {
+      if (ri === 1) return;
+      // 遍历meta,拼成数据
+      const object = {};
+      const excelValues = row.values;
+      for (const i of meta) {
+        const { index, column, turn } = i;
+        if (_.get(excelValues, index) && column) {
+          let value = _.get(excelValues, index);
+          if (turn) value = this[turn](i);
+          object[column] = value;
+        }
+      }
+      arr.push(object);
+    });
+    const errorArray = [];
+    for (const i of arr) {
+      try {
+        // try catch保证能一直往里插,把错误回收到一起统一显示
+        // TODO:此处await应该可以取消,但是可能就呜呜呜呜一顿添加数据了.可能没有问题,也可能会爆炸,需要试试
+        await this.ctx.service.create.index({ table }, i);
+      } catch (error) {
+        errorArray.push(i);
+      }
+    }
+    if (errorArray.length > 0) return errorArray;
+  }
+
+  /**
+   * 考试相关特殊导入
+   * @param {Object} param {uri} 上传的文件地址
+   */
+  async updateExaminee({ uri }) {
+    const file = await this.getFile(uri);
+    const workbook = new Excel.Workbook();
+    await workbook.xlsx.load(file.data);
+    const sheet = workbook.getWorksheet(1);
+    let meta = this.getExamineeMeta();
+    // 获取表头
+    const heads = sheet.getRow(1).values;
+    for (const h of meta) {
+      if (!_.get(h, 'key')) continue;
+      // 直接找索引,下面也是用索引找
+      const r = heads.findIndex(f => f === h.key);
+      if (r >= 0) h.index = r;
+    }
+    // 将没有索引的踢出去,别影响下面,可能报异常会炸
+    meta = meta.filter(i => _.get(i, 'index'));
+    const arr = [];
+    sheet.eachRow((row, ri) => {
+      if (ri === 1) return;
+      // 遍历meta,拼成数据
+      const object = {};
+      const excelValues = row.values;
+      for (const i of meta) {
+        const { index, column } = i;
+        if (_.get(excelValues, index) && column) {
+          const value = _.get(excelValues, index);
+          if (column === 'exam_date') {
+            const date = moment(value).format('YYYY-MM-DD');
+            object[column] = date;
+          } else {
+            object[column] = value;
+          }
+        }
+      }
+      arr.push(object);
+    });
+    const errorList = [];
+    // 去修改
+    for (const i of arr) {
+      const { card, testsite_num } = i;
+      if (!card || !testsite_num) {
+        errorList.push(i);
+        continue;
+      }
+      const table = 'examination_examinee';
+      const query = { card, testsite_num };
+      const body = i;
+      try {
+        await this.ctx.service.update.index({ table }, query, body);
+      } catch (error) {
+        errorList.push(i);
+      }
+    }
+    if (errorList.length > 0) return errorList;
+    return 'ok';
+  }
+
+  getExamineeMeta() {
+    return [
+      { key: '姓名', column: 'name' },
+      { key: '性别', column: 'gender' },
+      { key: '身份证号', column: 'card' },
+      { key: '联系电话', column: 'phone' },
+      { key: '准考证号', column: 'exam_num' },
+      { key: '公安机关', column: 'police_office' },
+      { key: '考试成绩', column: 'exam_achieve' },
+      { key: '体能', column: 'stamina' },
+      { key: '考试等级', column: 'exam_grade' },
+      { key: '考试日期', column: 'exam_date' },
+      { key: '考试时间', column: 'exam_time' },
+      { key: '考点编码', column: 'testsite_num' },
+      { key: '考试地点名称', column: 'exam_addr' },
+      { key: '座位号', column: 'seat_num' },
+      { key: '场次', column: 'exam_play' },
+      { key: '参考区县', column: 'district' },
+      { key: '考试类型', column: 'exam_type' },
+    ];
+  }
+
+  async staffBase({ uri }) {
+    const file = await this.getFile(uri);
+    const workbook = new Excel.Workbook();
+    await workbook.xlsx.load(file.data);
+    const sheet = workbook.getWorksheet(1);
+    let meta = this.getStaffBase();
+    // 获取表头
+    const heads = sheet.getRow(1).values;
+    for (const h of meta) {
+      if (!_.get(h, 'key')) continue;
+      // 直接找索引,下面也是用索引找
+      const r = heads.findIndex(f => f === h.key);
+      if (r >= 0) {
+        h.index = r;
+      }
+    }
+    // 将没有索引的踢出去,别影响下面,可能报异常会炸
+    meta = meta.filter(i => _.get(i, 'index'));
+    const arr = [];
+    // 处理图片
+    const sheetImageInfo = sheet.getImages();
+    const imgs = _.compact(
+      sheetImageInfo.map(i => {
+        const { imageId, range } = i;
+        const row = _.get(range, 'tl.nativeRow');
+        const col = _.get(range, 'tl.nativeCol');
+        // const column = _.get
+        if (row && col) return { row, col: col + 1, imageId };
+      })
+    );
+    sheet.eachRow((row, ri) => {
+      if (ri === 1) return;
+      // 遍历meta,拼成数据
+      const object = {};
+      const excelValues = row.values;
+      for (const i of meta) {
+        const { index, column } = i;
+        if (_.get(excelValues, index) && column) {
+          const value = _.get(excelValues, index);
+          if (column === 'birth' || column === 'acceptance_date') {
+            const date = moment(value).format('YYYY-MM-DD');
+            object[column] = date;
+          } else {
+            object[column] = value;
+          }
+        }
+      }
+      arr.push(object);
+    });
+
+    // 上传前的准备工作.因为函数里有很多变量是这个函数中的全局变量,移出去的话会有很多参数,所以昨成立函数中的函数
+    const toUpload = async (i, key, card) => {
+      const rowIndex = i + 1;
+      const metaObject = meta.find(f => f.column === key);
+      if (!metaObject) return;
+      const colIndex = metaObject.index;
+      const imgObject = imgs.find(f => f.row === rowIndex && f.col === colIndex);
+      const imgId = _.get(imgObject, 'imageId');
+      if (imgId || imgId === 0) {
+        const img = workbook.getImage(imgId);
+        const uri = `baoan_staffBase_card/${card}/upload`;
+        img.uri = uri;
+        img.name = key === 'id_just' ? 'front' : 'back';
+        const url = `http://127.0.0.1:${process.env.NODE_ENV === 'development' ? '9999' : '80'}/files/server/upload`;
+        const res = await this.uploadImage(url, img);
+        return res;
+      }
+    };
+    const errorList = [];
+    for (let i = 0; i < arr.length; i++) {
+      const object = arr[i];
+      const { name, card } = object;
+      if (!name || !card) {
+        // 缺少名和身份证号的不加
+        errorList.push(object);
+        continue;
+      }
+      const id_just = _.get(object, 'id_just');
+      const id_back = _.get(object, 'id_back');
+      if (!id_just) {
+        // 没有,获取
+        const result = await toUpload(i, 'id_just', card);
+        object.id_just = result;
+      }
+      if (!id_back) {
+        const result = await toUpload(i, 'id_back', card);
+        object.id_back = result;
+      }
+      // 身份证后六位为密码
+      const password = card.substring(12);
+      if (password) object.password = password;
+      // 加人
+      let baseInfo;
+      try {
+        baseInfo = await this.ctx.service.create.index({ table: 'security_guard_base' }, object);
+        if (baseInfo) baseInfo = baseInfo.data;
+      } catch (error) {
+        console.error('保安人员添加失败');
+        this.ctx.logger.error('保安人员添加失败');
+        errorList.push({ ...object, reason: '保安人员添加失败' });
+        continue;
+      }
+
+      try {
+        if (!_.get(baseInfo, 'id')) continue;
+        await this.ctx.service.create.index(
+          { table: 'security_guard_collect' },
+          { name: _.get(baseInfo, 'name'), security_guard_id: _.get(baseInfo, 'id'), data: _.get(baseInfo, 'id_just'), type: '人像' }
+        );
+        await this.ctx.service.update.index({ table: 'security_guard_base' }, { id: _.get(baseInfo, 'id') }, { collect_photo: '已采集' });
+      } catch (error) {
+        console.error('保安人员采集信息添加失败');
+        this.ctx.logger.error('保安人员采集信息添加失败');
+        errorList.push({ ...object, reason: '保安人员采集信息添加失败' });
+        if (_.get(baseInfo, 'id')) {
+          await this.ctx.service.delete.index({ table: 'security_guard_base' }, { id: _.get(baseInfo, 'id') });
+        }
+      }
+    }
+    if (errorList.length > 0) return errorList;
+    return 'ok';
+  }
+  getStaffBase() {
+    return [
+      { key: '是否上班', column: 'is_class' },
+      { key: '主键', column: 'id' },
+      { key: '姓名', column: 'name' },
+      { key: '曾用名', column: 'beforeName' },
+      { key: '身份证号', column: 'card' },
+      { key: '性别', column: 'gender' },
+      { key: '民族', column: 'nation' },
+      { key: '出生日期', column: 'birth' },
+      { key: '政治面貌', column: 'politics' },
+      { key: '文化程度', column: 'education' },
+      { key: '兵役状况', column: 'soldier' },
+      { key: '婚姻状况', column: 'marriage' },
+      { key: '血型', column: 'blood' },
+      { key: '身高', column: 'height' },
+      { key: '保安员等级', column: 'grade' },
+      { key: '驾驶证号', column: 'drive_num' },
+      { key: '准驾车型', column: 'drive_type' },
+      { key: '健康状态', column: 'health' },
+      { key: '户籍省市县', column: 'house_city' },
+      { key: '户籍/居住证派出所名称', column: 'house_police' },
+      { key: '微信公众号OpenID', column: 'gopenid' },
+      { key: '微信统一id', column: 'unionid' },
+      { key: '是否加入人才库', column: 'is_talent' },
+      { key: '微信小程序绑定的用户OpenID', column: 'openid' },
+      { key: '报名企业', column: 'sign_company' },
+      { key: '培训机构编码', column: 'train_num' },
+      { key: '现住地省市县', column: 'onhouse_city' },
+      { key: '户籍地详址', column: 'house_address' },
+      { key: '现住地详址', column: 'house_onaddress' },
+      { key: '家庭主要成员', column: 'home_member' },
+      { key: '教育经历', column: 'education_experience' },
+      { key: '工作经历', column: 'work_experience' },
+      { key: '注销人', column: 'cancel_personal' },
+      { key: '注销原因', column: 'cancel_reason' },
+      { key: '注销时间', column: 'cancel_date' },
+      { key: '状态', column: 'status' },
+      { key: '受理人', column: 'acceptance_personal' },
+      { key: '受理公安机关', column: 'acceptance_police' },
+      { key: '受理时间', column: 'acceptance_date' },
+      { key: '身份证正面', column: 'id_just' },
+      { key: '身份证反面', column: 'id_back' },
+      { key: '联系电话', column: 'phone' },
+      { key: '登录密码', column: 'password' },
+      { key: '创建时间', column: 'create_time' },
+      { key: '更新时间', column: 'update_time' },
+      { key: '采集照片', column: 'collect_photo' },
+      { key: '采集指纹', column: 'collect_fingerprint' },
+      { key: '审核结果', column: 'examine_status' },
+      { key: '审批结果', column: 'approve_status' },
+      { key: '注销状态', column: 'cancel_status' },
+    ];
+  }
+  /**
+   * 上传图片(服务端=>服务端)
+   * @param {String} uri 上传路径
+   * @param {Object} img 从sheet取出的每项的图片object
+   */
+  async uploadImage(uri, img) {
+    const base64 = this.turnImageToBase64(img);
+    delete img.buffer;
+    img.code = base64;
+    const res = await this.http.$post(uri, img);
+    if (res && res.uri) {
+      return res.uri;
+    }
+  }
+
+  /**
+   * 转换图片为base64
+   * @param {Object} object excel获取的图片object
+   * @property extension  后缀,文件类型
+   * @property buffer 图片内容,不含头部信息的,
+   */
+  turnImageToBase64(object = {}) {
+    const { extension, buffer } = object;
+    if (extension && buffer) {
+      const suffix = object.extension;
+      const ib = object.buffer.toString('base64');
+      const base64 = `data:image/${suffix};base64,${ib}`;
+      return base64;
+    }
+  }
+
+  /**
+   * 上传保安人员
+   * @param {String} uri 文件路径
+   */
+  async securityGuard({ uri }) {
+    assert(uri, '缺少文件地址,无法读取文件');
+    const meta = require('../public/securityGuardImportMeta');
+    const metaArr = [ ...meta.base, ...meta.cert ];
+    const file = await this.getFile(uri);
+    const workbook = new Excel.Workbook();
+    await workbook.xlsx.load(file.data);
+    const sheet = workbook.getWorksheet(1);
+    const head = _.get(sheet.getRow(1), 'values', []);
+    for (let i = 0; i < head.length; i++) {
+      const e = head[i];
+      if (!e) continue;
+      const r = metaArr.find(f => f.zh === e);
+      if (r) r.index = i;
+    }
+    // 需要整理出 必填字段,默认字段,检查token字段,需要执行函数来赋值的字段
+    /**
+     * 从excel获取数据的字段
+     */
+    const getFormExcelArray = metaArr.filter(f => f.index);
+    /**
+     * 默认值字段
+     */
+    const defArray = metaArr.filter(f => f.default);
+    const defObject = {};
+    for (const i of defArray) {
+      const { key, default: def } = i;
+      defObject[key] = def;
+    }
+    const user = this.ctx.user;
+    if (!user) {
+      new BusinessError(ErrorCode.NOT_LOGIN, '未找到操作人信息');
+    }
+    /**
+     * checkToken部分处理
+     */
+    const ctArray = metaArr.filter(f => f.checkToken);
+    for (const i of ctArray) {
+      const { method, key } = i;
+      defObject[key] = method(user);
+    }
+    /**
+     * 错误列表
+     */
+    const errorList = [];
+    /**
+     * 数据列表
+     */
+    const dataList = [];
+    /**
+     * 获取excel的数据
+     */
+    sheet.eachRow(async (row, index) => {
+      if (index !== 1) {
+        const values = row.values;
+        let obj = {};
+        for (const m of getFormExcelArray) {
+          const { required, key, index, zh, format } = m;
+          const value = values[index];
+          if (required && !value) {
+            // 必填且没值的情况
+            errorList.push({ message: `第${index}行数据,缺少必填项 ${zh};` });
+            continue;
+          }
+          if (format) {
+            if (_.isFunction(format)) obj[key] = format(value);
+          } else obj[key] = value;
+        }
+        obj = Object.assign(obj, defObject);
+        dataList.push(obj);
+      }
+    });
+    if (errorList.length > 0) return errorList;
+    /**
+     * 保安员基础表字段
+     */
+    const baseMeta = meta.base.map(i => i.key);
+    /**
+     * 保安员证书表字段
+     */
+    const certMeta = meta.cert.map(i => i.key);
+    for (const eData of dataList) {
+      const baseObject = _.pick(eData, baseMeta);
+      const certObject = _.pick(eData, certMeta);
+      const rd = await this.fetchService.index({ table: 'security_guard_base' }, { card: baseObject.card });
+      if (!rd.data) {
+        // 创建
+        /**
+         * 公安机关机构
+         */
+        let policeDepartmentObject;
+        /**
+         * 创建后的保安人员基础信息
+         */
+        let securityBaseObject;
+        /**
+         * 创建后的保安证信息
+         */
+        let certificatesBaseObject;
+        // 补全编码
+        try {
+          policeDepartmentObject = await this.ctx.service.securityGuard.base.getHousePoliceCode(eData);
+          if (policeDepartmentObject) {
+            baseObject.house_police_code = policeDepartmentObject.num;
+          } else {
+            // 业务逻辑错误.并非异常
+            errorList.push({ message: `${baseObject.name}: 未找到 户籍/居住证派出所编码!请查询公安机构名称数据与表格数据是否一致;` });
+            continue;
+          }
+        } catch (error) {
+          errorList.push({ message: `${baseObject.name}: 补全 户籍/居住证派出所编码 失败!;` });
+          continue;
+        }
+        // 创建保安员信息
+        try {
+          const baseResult = await this.createService.index({ table: 'security_guard_base' }, baseObject);
+          if (baseResult) securityBaseObject = baseResult.data;
+        } catch (error) {
+          errorList.push({ message: `${baseObject.name}: 保安员信息创建 失败!;` });
+          continue;
+        }
+        // 将保安员id放入证的数据中
+        certObject.security_guard_id = securityBaseObject.id;
+        // 创建保安证的信息;相关信息补全及创建
+        // 如果是业务逻辑异常,则内部catch不需要向errorList输出信息.反之则需要输出带有 '失败' 的字样.提示是接口相关错误,而不是逻辑错误
+        // 嵌套try...catch如果想到上一层try...catch中.只能使用通常异常,使用工具异常会直接跳出去
+        try {
+          /**
+           * 保安证信息补全,企业查询.错误是否已经输出的判断变量
+           */
+          let companyHaveFault = false;
+          try {
+            if (certObject.company_name) {
+              // 查询保安证所在企业的企业id,并赋值
+              const companyResutl = await this.fetchService.index({ table: 'company_base' }, { name: certObject.company_name });
+              if (companyResutl.data) {
+                certObject.company_id = _.get(companyResutl, 'data.id');
+              } else {
+                // 业务逻辑错误.并非异常,但是需要抛出异常,触发删除
+                errorList.push({ message: `${baseObject.name}补全保安证信息: 未找到 ${certObject.company_name} 相关信息!请查询是否有该企业信息;` });
+                companyHaveFault = true;
+                throw '接口错误';
+              }
+            }
+          } catch (error) {
+            if (!companyHaveFault) errorList.push({ message: `${baseObject.name}补全保安证信息: 查询 ${certObject.company_name} 相关信息 失败!;` });
+            throw '接口错误';
+          }
+          // 创建保安证
+          try {
+            const certResult = await this.createService.index({ table: 'certificates_base' }, certObject);
+            if (certResult) certificatesBaseObject = certResult.data;
+          } catch (error) {
+            errorList.push({ message: `${baseObject.name}: 保安证信息创建 失败!;` });
+            throw '接口错误';
+          }
+        } catch (error) {
+          // 删除保安员信息
+          await this.deleteService.index({ table: 'security_guard_base' }, { id: securityBaseObject.id });
+          continue;
+        }
+      } else {
+        errorList.push(`已存在 ${baseObject.name} 保安员,不予处理`);
+      }
+    }
+    return { errorList };
+  }
+
+  /**
+   * excel批量办理入职
+   * @param {String} uri 文件路径
+   */
+  async guardWork({ uri }) {
+    assert(uri, '缺少文件地址,无法读取文件');
+    const meta = require('../public/guardWorkMeta');
+    const file = await this.getFile(uri);
+    const workbook = new Excel.Workbook();
+    await workbook.xlsx.load(file.data);
+    const sheet = workbook.getWorksheet(1);
+    const head = _.get(sheet.getRow(1), 'values', []);
+    for (let i = 0; i < head.length; i++) {
+      const e = head[i];
+      if (!e) continue;
+      const r = meta.find(f => f.zh === e);
+      if (r) r.index = i;
+    }
+    /**
+     * 判断当前用户是否是企业用户的变量
+     */
+    let is_company = false;
+    /**
+     * 从excel获取数据的字段
+     */
+    const getFormExcelArray = meta.filter(f => f.index);
+    // 需要判断,如果当前用户不是企业,则必须要有 企业名称 列,否则直接提示错误
+    if (this.ctx.user.table !== 'company_base') {
+      const r = getFormExcelArray.find(f => f.key === 'company_name');
+      if (!r) throw new BusinessError(ErrorCode.DATA_INVALID, '非企业用户,缺少企业名称会导致入职缺少企业相关信息!拒绝导入!');
+    } else is_company = true;
+    /**
+     * 默认值字段
+     */
+    const defArray = meta.filter(f => f.default);
+    /**
+     * 查表处理
+     */
+    const tableArray = meta.filter(f => f.table);
+    const errorList = [];
+    const dataList = [];
+    /**
+     * 获取excel的数据
+     */
+    sheet.eachRow(async (row, index) => {
+      if (index !== 1) {
+        const values = row.values;
+        const obj = {};
+        for (const m of getFormExcelArray) {
+          const { required, key, index, zh, format } = m;
+          const value = values[index];
+          if (required && !value) {
+            // 必填且没值的情况
+            errorList.push({ message: `第${index}行数据,缺少必填项 ${zh};` });
+            continue;
+          }
+          if (format) {
+            if (_.isFunction(format)) obj[key] = format(value);
+          } else obj[key] = value;
+        }
+        dataList.push(obj);
+      }
+    });
+    if (errorList.length > 0) return errorList;
+    // 处理default值
+    for (const d of dataList) {
+      for (const m of defArray) {
+        const { key, default: def } = m;
+        if (_.isFunction(def)) d[key] = def(d[key]);
+        else d[key] = def;
+      }
+    }
+
+    // 取值函数,就这用,就不外面写了
+    const getValue = (column, object) => {
+      const { key, from } = column;
+      const obj = { key };
+      obj.value = object[from] || object[key];
+      return obj;
+    };
+
+    // 补充数据,需要从别的表拽来的那些
+    for (const d of dataList) {
+      for (const object of tableArray) {
+        const { query, table, columns, zh } = object;
+        // 当前要处理的数据源
+        let originData;
+        if (table === 'company_base' && is_company) {
+          // 补充企业信息,且当前用户为企业时:则不需要请求处理,直接从this.ctx.user中取出来就好
+          d.company_name = this.ctx.user.name;
+          originData = _.cloneDeep(this.ctx.user);
+        } else {
+          const q = {};
+          for (const key in query) {
+            const v = query[key];
+            if (_.isFunction(v)) q[key] = v(d);
+            else q[key] = v;
+          }
+          const r = await this.fetchService.index({ table }, q);
+          if (r.data) originData = r.data;
+          else {
+            errorList.push({ message: `${d.name}: 未找到 ${zh} 信息` });
+            continue;
+          }
+        }
+        // 获取到了数据源,开始补值
+        for (const column of columns) {
+          const { key, value } = getValue(column, originData);
+          d[key] = value;
+        }
+      }
+    }
+    if (errorList.length > 0) return { errorList };
+    const res = await this.ctx.service.securityGuard.work.toWork({ data: dataList });
+    return { errorList: res };
+  }
+}
+
+module.exports = ImportService;

+ 112 - 0
app/service/login.js

@@ -0,0 +1,112 @@
+'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 dataToDb = require('../public/dataToDb');
+const jwt = require('jsonwebtoken');
+
+// 登陆相关
+class LoginService extends CrudService {
+  constructor(ctx) {
+    super(ctx, 'login');
+    this.http = this.ctx.service.util.httpUtil;
+    this.spMark = this.ctx.service.special;
+    this.prefix = '/db';
+    this.time = '30m';
+    this.secret = this.config.jwt.secret;
+
+  }
+
+  /**
+   * 账号登录,都从这里走
+   * 优先使用微信登陆
+   * @param {Object} params 表名,直接拿table用
+   * @param {Object} body 登陆参数
+   * @property phone 电
+   * @property password 密码
+   * @property openid 微信id
+   * @property wx 是否微信登陆
+   */
+  async login({ table }, { phone, password, openid, wx = false }) {
+    assert(table, '缺少登陆角色');
+    assert((phone && password) || openid, '缺少登陆信息');
+    const query = {};
+    if (openid) {
+      // TODO微信登录查询
+      query.openid = openid;
+    } else {
+      // 手机号密码登录
+      query.phone = phone;
+      password = dataToDb.toSecret(password);
+    }
+    let data = await this.http.$post(`${this.prefix}/fetch`, query, { table });
+    if (!data) throw new BusinessError(ErrorCode.USER_NOT_EXIST, '未找到用户');
+    if (openid) {
+      data = this.spMark.resetDataToFront(data, table);
+      // 微信端openid登陆需要token,和token内容,因为没有jwt解密.
+      return { token: this.getJwt(table, data), ...this.getTokenData(table, data) };
+    }
+    // 获取账户密码
+    const { password: ap } = data;
+    // 账户密码 与 输入密码 对比
+    const r = _.isEqual(ap, password);
+    if (r) {
+      // 密码正确
+      data = this.spMark.resetDataToFront(data, table);
+      if (!wx) { return this.getJwt(table, data); }
+      // 微信登陆需要加密内容和token
+      return { token: this.getJwt(table, data), ...this.getTokenData(table, data) };
+    }
+    throw new BusinessError(ErrorCode.BAD_PASSWORD, '密码错误');
+
+  }
+
+  /**
+   * 登陆加密
+   * @param {String} table 表名
+   * @param {Any} data jwt加密的东西
+   */
+  getJwt(table, data) {
+    const keys = this.getTokenData(table, data);
+    return jwt.sign(keys, this.secret);
+  }
+
+  /**
+   * 筛选jwt加密内容
+   * @param {String} table 表名
+   * @param {Any} data jwt加密的内容
+   */
+  getTokenData(table, data) {
+    const keys = { table };
+    if (data.id) keys.id = data.id;
+    return keys;
+  }
+
+
+  /**
+   * 验证token
+   * @param {Object} token token字符串
+   */
+  async checkJwt({ token }) {
+    if (!token) throw new BusinessError(ErrorCode.ACCESS_DENIED, '缺少秘钥,拒绝访问');
+    const errorList = [
+      { key: 'jwt expired', word: '秘钥已过期,请重新登陆' },
+      { key: 'invalid signature', word: '秘钥错误,请检查秘钥' },
+      { key: 'JSON at position', word: '秘钥错误,请检查秘钥' },
+      { key: 'invalid token', word: '秘钥错误,请检查秘钥' },
+    ];
+    try {
+      const r = jwt.verify(token, this.secret);
+      if (r) return r; // 如果过期将返回false
+      return false;
+    } catch (e) {
+      const { message } = e;
+      const r = errorList.find(f => message.includes(f.key));
+      if (r) throw new BusinessError(ErrorCode.ACCESS_DENIED, r.word);
+      else throw new BusinessError(ErrorCode.ACCESS_DENIED, '秘钥产生位置错误,检测失败');
+    }
+  }
+}
+
+module.exports = LoginService;

+ 88 - 0
app/service/query.js

@@ -0,0 +1,88 @@
+'use strict';
+const _ = require('lodash');
+const Service = require('egg').Service;
+const columns = require('../public/query');
+const { BusinessError, ErrorCode } = require('naf-core').Error;
+// 通用列表/多查/全查 查询实现
+class QueryService extends Service {
+  constructor(ctx) {
+    super(ctx, 'query');
+    this.http = this.ctx.service.util.httpUtil;
+    this.spMark = this.ctx.service.special;
+    this.prefix = '/db';
+    // 默认查询条件
+    this.defaultType = 'like';
+    // 强制全等 key 列表
+    this.eqList = [ 'status' ];
+  }
+
+  /**
+   * 常规查询
+   * @param {Object} param0 表名
+   * @param {Object} param1 正常query的参数,保函skip(指的是page),limit
+   */
+  async index({ table }, { skip = 1, limit, ...query } = {}) {
+    query = this.dealQuery(table, query);
+    const data = await this.http.$post(`${this.prefix}/query`, query, { table, page: skip, size: limit });
+    if (data && data.code) {
+      throw new BusinessError(ErrorCode.SERVICE_FAULT, data.message);
+    }
+    const { list, total } = data;
+    // 在dbToData中找是否有该表需要特殊处理的字段
+    const resetList = this.spMark.resetDataToFront(list, table);
+    return { data: resetList || [], total };
+  }
+
+
+  /**
+   * 处理参数,给java部分
+   * 最后形成数组,数组每个元素都是条件,包括查询与排序
+   * [
+   *  {key:xxx,value:xxx,type:xxx}
+   * ]
+   * @param {String} table 表名
+   * @param {Object} query 参数
+   */
+  dealQuery(table, query) {
+    const arr = _.get(columns, table, []);
+    let order = [];
+    let aboutQuery = [];
+    if (arr.length > 0) {
+      // orderBy分离开,因为与查询条件没关系,与最后数据的过滤有关
+      order = arr.filter(f => f.type === 'orderBy');
+      // 取arr,order,between的差集就是正常处理的内容
+      aboutQuery = _.differenceWith(arr, [ ...order ]);
+    }
+    const nq = [];
+    for (const key in query) {
+      let obj = { key, value: query[key], type: this.defaultType };
+      const r = aboutQuery.find(f => f.key === key);
+      if (r) obj.type = _.get(r, 'type', this.defaultType);
+      // 特殊处理:如果该key在eqList中,无论什么匹配类型,都改成eq
+      if (this.eqList.includes(key)) obj.type = 'eq';
+      // 处理key中是否带有@:范围查询的标志,需要把@后面的start/end砍掉
+      obj = this.spMark.dealMarkQuery(_.cloneDeep(obj));
+      if (!obj.key) continue;
+      if (obj.value === 'null') {
+        // 特殊处理,如果value为null字符串,需要数据库端进行特殊处理,在这里把类型转换成null类型
+        obj.type = 'null';
+      } else if (_.isArray(obj.value)) {
+        // 检查值,如果为数组,则需要将type变成in
+        obj.value = obj.value.join(',');
+        obj.type = 'in';
+      }
+      nq.push(obj);
+
+    }
+    // 处理orderBy
+    for (const i of order) {
+      const { key, value } = i;
+      nq.push({ key, value, type: 'orderBy' });
+    }
+
+    return nq;
+  }
+
+}
+
+module.exports = QueryService;

+ 28 - 0
app/service/securityGuard/base.js

@@ -0,0 +1,28 @@
+'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 BaseService extends CrudService {
+  constructor(ctx) {
+    super(ctx, 'base');
+    this.fetchService = this.ctx.service.fetch;
+  }
+
+  /**
+   * 用 house_police 去换对应公安机构的编码作为 house_police_code
+   * @param {Object} data body中的数据
+   */
+  async getHousePoliceCode(data) {
+    if (!data) return;
+    const { house_police } = data;
+    if (!house_police) return;
+    const table = 'police_department';
+    const query = { name: house_police };
+    const res = await this.fetchService.index({ table }, query);
+    return _.get(res, 'data');
+  }
+}
+
+module.exports = BaseService;

+ 51 - 0
app/service/securityGuard/work.js

@@ -0,0 +1,51 @@
+'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 WorkService extends CrudService {
+  constructor(ctx) {
+    super(ctx, 'work');
+    this.createService = this.ctx.service.create;
+    this.updateService = this.ctx.service.update;
+    this.deleteService = this.ctx.service.delete;
+  }
+
+  /**
+   * 批量办理入职的实现(整理数据后才能执行)
+   * @param {Array} data 数据[object]
+   */
+  async toWork({ data }) {
+    if (_.isArray(data) && data.length <= 0) {
+      throw new BusinessError(ErrorCode.DATA_INVALID, '数据错误');
+    }
+    const errorList = [];
+    // 创建入职信息
+    for (const d of data) {
+      let resultData;
+      try {
+        const res = await this.createService.index({ table: 'company_baoan_work' }, d);
+        if (res.data) resultData = res.data;
+      } catch (error) {
+        errorList.push({ message: `${d.name}:创建入职信息失败` });
+        continue;
+      }
+      if (resultData) {
+        try {
+          // 创建入职信息,随后将保安证的企业与企业id修改
+          const body = _.pick(d, [ 'company_id', 'company_name' ]);
+          await this.updateService.index({ table: 'certificates_base' }, { card: d.card }, body);
+        } catch (error) {
+          errorList.push({ message: `${d.name}:修改保安证 失败!` });
+          await this.deleteService.index({ table: 'company_baoan_work' }, { id: resultData.id });
+        }
+      }
+    }
+
+    return errorList;
+  }
+}
+
+module.exports = WorkService;

+ 135 - 0
app/service/special.js

@@ -0,0 +1,135 @@
+'use strict';
+const _ = require('lodash');
+const Service = require('egg').Service;
+const { BusinessError, ErrorCode } = require('naf-core').Error;
+// 数据进库
+const dataToDb = require('../public/dataToDb');
+// 数据出库
+const dbToData = require('../public/dbToData');
+// 特殊符号处理
+class SpecialService extends Service {
+  constructor(ctx) {
+    super(ctx, 'special');
+    // 特殊符号列表
+    this.list = [ '@', '^' ];
+    // in的分隔符
+    this.inSplit = ',';
+  }
+
+  /**
+   * query的特殊符号处理
+   * 有新的符号,就在else那里处理
+   * @param {Object} obj 参数对象
+   */
+  dealMarkQuery(obj) {
+    // 处理严谨点,要是真有人改了代码,不传参就调用别在这报错,工具函数,牵一发动全身,危险
+    const mark = this.hasMark(obj.key);
+    if (obj && obj.key && mark) {
+      const key = obj.key;
+      let sp = [];
+      sp = key.split(mark);
+      if (mark === '@') {
+        // ${key}@start/end 形式
+        // 单独处理下value,不一定都是日期格式,所以不是日期直接给过去,是日期就转换;
+        obj.key = _.head(sp) || key;
+        if (_.last(sp) === 'start') {
+          obj.type = 'gte';
+        } else if (_.last(sp) === 'end') {
+          obj.type = 'lte';
+        }
+      } else if (mark === '^') {
+        // ^${key}的形式; value 统一用','号分隔
+        // 转换成 in 的 type
+        obj.type = 'in';
+        obj.key = _.last(sp);
+      } else new BusinessError(ErrorCode.DATA_INVALID, '未知特殊符号,请联系管理员通知开发人员');
+    }
+    return obj;
+  }
+
+  /**
+   * fetch特殊符号处理
+   * @param {Object} obj 请求参数,和query的处理不一样,这个obj没经过任何处理,是直接从前端传来的 Object类型
+   */
+  dealMarkFetch(obj) {
+    for (const key in obj) {
+      // String.split(reg/string)是分割,不是扔了,没有分割的话起码有个原来的值,别干了啊,干了就没了
+      let sp = [];
+      if (key.includes('@')) {
+        // 时间需要改值
+        sp = key.split('@');
+        obj[_.head(sp)] = obj[key];
+      }
+      // 分割出来了,原来的删了,上面增加完了
+      if (sp.length > 1) delete obj[key];
+    }
+    return obj;
+  }
+
+  /**
+   * 查询 键名 是否带 特殊符号,如果带,返回对应的 特殊符号;不带,返回false
+   * @param {String} key 带特殊符号的键名
+   */
+  hasMark(key) {
+    let result = false;
+    for (const mark of this.list) {
+      if (key.includes(mark)) {
+        result = mark;
+        break;
+      } else continue;
+    }
+    return result;
+  }
+
+  /**
+   * 转换日期格式,进oracle能查询--吐了,真吐了
+   * @param {String} str 日期字符串
+   */
+  changeDate(str) {
+    return dataToDb.toUnix(str);
+  }
+
+  /**
+   * 处理是否需要转换字段的值, 按规定处理 出库 数据
+   * @param {Object/Array} object 查询后的结果
+   * @param {String} table 表名
+   */
+  resetDataToFront(object, table) {
+    const method = _.get(dbToData, table);
+    if (!method) return object;
+    if (_.isArray(object)) {
+      // 数组,逐条处理
+      object = object.map(i => {
+        i = method(i);
+        return i;
+      });
+    } else if (_.isObject(object)) {
+      // 对象,单对象处理
+      object = method(object);
+    }
+    return object;
+  }
+
+  /**
+   * 处理是否需要转换字段的值, 按规定处理 入库 数据
+   * @param {Object/Array} object 入库的数据
+   * @param {String} table 表名
+   */
+  resetDataToDB(object, table) {
+    const method = _.get(dataToDb, table);
+    if (!method) return object;
+    if (_.isArray(object)) {
+      // 数组,逐条处理
+      object = object.map(i => {
+        i = method(i);
+        return i;
+      });
+    } else if (_.isObject(object)) {
+      // 对象,单对象处理
+      object = method(object);
+    }
+    return object;
+  }
+}
+
+module.exports = SpecialService;

+ 33 - 0
app/service/statis.js

@@ -0,0 +1,33 @@
+'use strict';
+const _ = require('lodash');
+const Service = require('egg').Service;
+const { BusinessError, ErrorCode } = require('naf-core').Error;
+// 独立的统计查询实现
+class QueryService extends Service {
+  constructor(ctx) {
+    super(ctx, 'statis');
+    this.http = this.ctx.service.util.httpUtil;
+    this.spMark = this.ctx.service.special;
+    this.prefix = '/db';
+  }
+
+  /**
+   * 统计
+   * @param {Object} param0 统计接口名称
+   * @param {Object} param1 正常query的参数
+   */
+  async index({ table }, { ...query } = {}) {
+    const data = await this.http.$get(`${this.prefix}/statis/${table}`, query, {});
+    if (data && data.code) {
+      throw new BusinessError(ErrorCode.SERVICE_FAULT, data.message);
+    }
+
+    // 在dbToData中找是否有该表需要特殊处理的字段
+    const resetList = this.spMark.resetDataToFront(data, table);
+
+    return { data: data || [] };
+  }
+
+}
+
+module.exports = QueryService;

+ 46 - 0
app/service/update.js

@@ -0,0 +1,46 @@
+'use strict';
+const { CrudService } = require('naf-framework-mongoose-free/lib/service');
+const { BusinessError, ErrorCode } = require('naf-core').Error;
+const _ = require('lodash');
+// 通用修改/范围修改
+class UpdateService extends CrudService {
+  constructor(ctx) {
+    super(ctx, 'update');
+    this.http = this.ctx.service.util.httpUtil;
+    this.spMark = this.ctx.service.special;
+    this.prefix = '/db';
+  }
+  /**
+   * 常规修改
+   * @param {Object} param0 表名
+   * @param {Object} query 不需要任何处理,也不需要加符号,先都是eq
+   * @param {Object} body 添加数据
+   */
+  async index({ table }, query, body) {
+    body = this.dealBody(table, body);
+    // 如果query中有id,则变成单修改.将其他参数过滤掉,优先id独存
+    if (_.get(query, 'id')) query = { id: _.get(query, 'id') };
+    const keys = Object.keys(query);
+    if (keys.length <= 0) throw new BusinessError(ErrorCode.SERVICE_FAULT, '不确定修改范围,拒绝操作');
+    const data = await this.http.$post(`${this.prefix}/update`, { data: body, query }, { table });
+    if (data && data.code) {
+      throw new BusinessError(ErrorCode.SERVICE_FAULT, data.message);
+    }
+    return { data };
+  }
+
+  /**
+   * 额外处理入库数据
+   * @param {String} table 表名
+   * @param {Object} body 数据
+   */
+  dealBody(table, body) {
+    if (Object.keys(body).length <= 0) throw new BusinessError(ErrorCode.BADPARAM, '缺少数据,请检查请求');
+    // 检查参数处理
+    body = this.spMark.resetDataToDB(body, table);
+
+    return body;
+  }
+}
+
+module.exports = UpdateService;

+ 70 - 0
app/service/usual.js

@@ -0,0 +1,70 @@
+'use strict';
+const _ = require('lodash');
+const assert = require('assert');
+const Service = require('egg').Service;
+const columns = require('../public/query');
+const { BusinessError, ErrorCode } = require('naf-core').Error;
+const moment = require('moment');
+//
+class UsualService extends Service {
+  constructor(ctx) {
+    super(ctx, 'usual');
+    this.http = this.ctx.service.util.httpUtil;
+    this.fetchService = this.ctx.service.fetch;
+    this.createService = this.ctx.service.create;
+    this.updateService = this.ctx.service.update;
+    this.wxService = this.ctx.service.wx;
+    this.prefix = '/db';
+  }
+
+  async reserve({ id }) {
+    assert(id, '缺少预约安排信息');
+    const data = await this.http.$post(`${this.prefix}/reserve`, { query: { id } });
+    if (data && data.code) {
+      throw new BusinessError(ErrorCode.SERVICE_FAULT, data.message);
+    }
+    let result = true;
+    if (data <= 0) result = false;
+    return { data: result };
+  }
+
+  /**
+   * 发送消息
+   * @param {Object} body 参数
+   * @property {Array} user_ids 用户列表
+   * @property {String} content 消息内容
+   */
+  async sendMessage({ user_ids, content }) {
+    if (!this.ctx.user) throw new BusinessError(ErrorCode.SERVICE_FAULT, '未获取发送人信息,无法发送信息');
+    // 找到对应的用户,且用户存在openid的数据
+    const templateId = 'vQi6KbytlFvyT6rLjeAwih5Ji6qMY2nBGXeMvNXrUkw';
+    const time = moment().format('YYYY-MM-DD HH:mm:ss');
+    const { wxServer } = this.app.config;
+    if (!wxServer) throw new BusinessError(ErrorCode.SERVICE_FAULT, '未设置已读地址');
+    const data = { templateId, data: { keyword2: { value: content }, keyword3: { value: time } } };
+    const warningMessage = { send_id: this.ctx.user.id, send_name: this.ctx.user.name };
+    for (const id of user_ids) {
+      const user = await this.fetchService.index({ table: 'security_guard_base' }, { id });
+      if (!user) continue;
+      const { gopenid: openid, unionid, id: receive_id, name: receive_name } = user.data;
+      // 发送消息
+      const msgData = {
+        ...warningMessage,
+        send_time: time,
+        receive_id,
+        receive_name,
+        content,
+      };
+      const msg = await this.createService.index({ table: 'warning_message' }, msgData);
+      if (!msg.data) throw new BusinessError(ErrorCode.SERVICE_FAULT, '创建消息失败');
+      const sendObject = { ...data, openid, unionid, url: `${wxServer}/api/usual/readMessage/${msg.data.id}` };
+      await this.wxService.sendTemplate(sendObject);
+    }
+  }
+
+  async readMessage({ id }) {
+    await this.updateService.index({ table: 'warning_message' }, { id }, { is_read: '1' });
+  }
+}
+
+module.exports = UsualService;

+ 105 - 0
app/service/util/http-util.js

@@ -0,0 +1,105 @@
+'use strict';
+const { AxiosService } = require('naf-framework-mongoose-free/lib/service');
+const { BusinessError, ErrorCode } = require('naf-core').Error;
+const { isNullOrUndefined } = require('naf-core').Util;
+const _ = require('lodash');
+
+//
+class HttpUtilService extends AxiosService {
+  constructor(ctx) {
+    super(ctx, {}, {});
+    this.dbServerPort = this.app.config.dbServerPort;
+    this.dbServerIp = this.app.config.dbServerIp;
+  }
+
+
+  // 替换uri中的参数变量
+  merge(uri, query = {}) {
+    const keys = Object.keys(query);
+    const arr = [];
+    for (const k of keys) {
+      arr.push(`${k}=${query[k]}`);
+    }
+    if (arr.length > 0) {
+      uri = `${uri}?${arr.join('&')}`;
+    }
+    return uri;
+  }
+
+  /**
+   * curl-get请求
+   * @param {String} uri 接口地址
+   * @param {Object} query 地址栏参数
+   * @param {Object} options 额外参数
+   */
+  async $get(uri, query, options) {
+    return this.toRequest(uri, null, query, options);
+  }
+
+  /**
+   * curl-post请求
+   * @param {String} uri 接口地址
+   * @param {Object} data post的body
+   * @param {Object} query 地址栏参数
+   * @param {Object} options 额外参数
+   */
+  async $post(uri, data = {}, query, options) {
+    return this.toRequest(uri, data, query, options);
+  }
+
+  /**
+   * curl-post请求
+   * @param {String} uri 接口地址
+   * @param {Object} data post的body
+   * @param {Object} query 地址栏参数
+   * @param {Object} options 额外参数
+   */
+  async $delete(uri, data = {}, query, options) {
+    options = { ...options, method: 'delete' };
+    return this.toRequest(uri, data, query, options);
+  }
+
+  async toRequest(uri, data, query, options) {
+    query = _.pickBy(query, val => val && val !== '' && val !== 'undefined' && val !== 'null');
+    if (!(query.page && query.size)) {
+      delete query.page;
+      delete query.size;
+    }
+    if (!uri) console.error('uri不能为空');
+    if (_.isObject(query) && _.isObject(options)) {
+      const params = query.params ? query.params : query;
+      options = { ...options, params };
+    } else if (_.isObject(query) && !query.params) {
+      options = { params: query };
+    } else if (_.isObject(query) && query.params) {
+      options = query;
+    }
+    // 是否多租户模式,需要改变headers
+    const headers = { 'content-type': 'application/json' };
+    let url = '';
+    if (uri.includes('http')) url = uri;
+    else url = this.merge(`http://${this.dbServerIp}:${this.dbServerPort}${uri}`, options.params);
+    let res = await this.ctx.curl(url, {
+      method: isNullOrUndefined(data) ? 'get' : 'post',
+      url,
+      data,
+      dataType: 'json',
+      headers,
+      ...options,
+    });
+    if (res.status === 200) {
+      res = res.data || {};
+      const { code, message } = res;
+      if (code) {
+        console.warn(`[${uri}] fail: ${code}-${message} `);
+        return { code, message };
+      }
+      if (_.isArray(res.data)) return { list: res.data };
+      return res.data;
+    }
+    const { status } = res;
+    console.warn(`[${uri}] fail: ${status}-${res.data.message} `);
+  }
+}
+
+module.exports = HttpUtilService;

+ 72 - 0
app/service/util/rabbitMq.js

@@ -0,0 +1,72 @@
+'use strict';
+
+const Service = require('egg').Service;
+const _ = require('lodash');
+
+class RabbitmqService extends Service {
+
+  constructor(ctx) {
+    super(ctx);
+    this.exType = 'topic';
+    this.durable = true;
+  }
+
+  // 接收消息
+  async receiveQueueMsg(ex) {
+    this.ctx.logger.info('调用mq的' + ex);
+    const self = this;
+    const { mq } = self.ctx;
+    if (mq) {
+      const ch = await mq.conn.createChannel();
+      await ch.assertExchange(ex, 'topic', { durable: true });
+      const q = await ch.assertQueue('', { exclusive: true });
+      await ch.bindQueue(q.queue, ex, '*');
+      await ch.consume(q.queue, msg => this.logMessage(msg, this), { noAck: true });
+    } else {
+      this.ctx.logger.error('!!!!!!没有配置MQ插件!!!!!!');
+    }
+  }
+
+  async logMessage(msg) {
+    const result = msg.content.toString();
+    const headers = msg.properties.headers;
+  }
+
+  // mission队列处理
+  async mission() {
+    const { mq } = this.ctx;
+    if (mq) {
+      const ch = await mq.conn.createChannel();
+      const queue = 'mission/baoan';
+      try {
+        // 创建队列:在没有队列的情况,直接获取会导致程序无法启动
+        await ch.assertQueue(queue, { durable: false });
+        await ch.consume(queue, msg => this.dealMission(msg), { noAck: true });
+      } catch (error) {
+        this.ctx.logger.error('未找到订阅的队列');
+      }
+    } else {
+      this.ctx.logger.error('!!!!!!没有配置MQ插件!!!!!!');
+    }
+  }
+  // 执行任务
+  async dealMission(bdata) {
+    if (!bdata) this.ctx.logger.error('mission队列中信息不存在');
+    let data = bdata.content.toString();
+    try {
+      data = JSON.parse(data);
+    } catch (error) {
+      this.ctx.logger.error('数据不是object');
+    }
+    const { service, method, project, ...others } = data;
+    const arr = service.split('.');
+    let s = this.ctx.service;
+    for (const key of arr) {
+      s = s[key];
+    }
+    s[method](others);
+
+  }
+}
+
+module.exports = RabbitmqService;

+ 88 - 0
app/service/util/util.js

@@ -0,0 +1,88 @@
+'use strict';
+const _ = require('lodash');
+const moment = require('moment');
+const { CrudService } = require('naf-framework-mongoose-free/lib/service');
+const { ObjectId } = require('mongoose').Types;
+const fs = require('fs');
+class UtilService extends CrudService {
+  constructor(ctx) {
+    super(ctx);
+    this.mq = this.ctx.mq;
+  }
+  async utilMethod(query, body) {
+    // 非开发模式不执行
+    if (process.env.NODE_ENV !== 'development') return;
+    console.log('in function:');
+    const res = await this.ctx.curl('http://baoan.fwedzgc.com:8090/api/position?name=刘睿峰', { dataType: 'json' });
+    const { data } = res.data;
+    const ids = data.map(i => i._id);
+    console.log(ids);
+    console.log(ids.length);
+    // for (const id of ids) {
+    //   const r = await this.ctx.curl(`http://baoan.fwedzgc.com:8090/api/position/${id}`, { method: 'DELETE', dataType: 'json', headers: { dkey: 'free' } });
+    //   console.log(r);
+    // }
+
+  }
+
+  async expertExport() {
+  }
+
+
+  dealQuery(query) {
+    return this.turnFilter(this.turnDateRangeQuery(query));
+  }
+
+  /**
+   * 将查询条件中模糊查询的标识转换成对应object
+   * @param {Object} filter 查询条件
+   */
+  turnFilter(filter) {
+    const str = /^%\S*%$/;
+    const keys = Object.keys(filter);
+    for (const key of keys) {
+      const res = key.match(str);
+      if (res) {
+        const newKey = key.slice(1, key.length - 1);
+        filter[newKey] = new RegExp(filter[key]);
+        delete filter[key];
+      }
+    }
+    return filter;
+  }
+  /**
+   * 将时间转换成对应查询Object
+   * @param {Object} filter 查询条件
+   */
+  turnDateRangeQuery(filter) {
+    const keys = Object.keys(filter);
+    for (const k of keys) {
+      if (k.includes('@')) {
+        const karr = k.split('@');
+        if (karr.length === 2) {
+          const type = karr[1];
+          if (type === 'start') {
+            if (filter[k] && filter[k] !== '') {
+              filter[karr[0]] = {
+                ..._.get(filter, karr[0], {}),
+                $gte: filter[k],
+              };
+            }
+
+          } else {
+            if (filter[k] && filter[k] !== '') {
+              filter[karr[0]] = {
+                ..._.get(filter, karr[0], {}),
+                $lte: filter[k],
+              };
+            }
+          }
+          delete filter[k];
+        }
+      }
+    }
+    return filter;
+  }
+
+}
+module.exports = UtilService;

+ 185 - 0
app/service/wx.js

@@ -0,0 +1,185 @@
+'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 WxService extends CrudService {
+  constructor(ctx) {
+    super(ctx, 'wx');
+    this.userCache = this.ctx.model.WxUserCache;
+    this.queryService = this.ctx.service.query;
+    this.updateService = this.ctx.service.update;
+    const { wx } = this.app.config;
+    this.wx = wx;
+  }
+
+  // 小程序登录
+  async appAuth({ js_code }) {
+    const { wxAppConfig } = this.app.config;
+    if (!wxAppConfig) return;
+    let url = 'https://api.weixin.qq.com/sns/jscode2session';
+    // let url = 'http://106.12.161.200/api/position';
+    let query = `?js_code=${js_code}`;
+    const keys = Object.keys(wxAppConfig);
+    for (const key of keys) {
+      query = `${query}&${key}=${wxAppConfig[key]}`;
+    }
+    url = `${url}${query}&grant_type=authorization_code`;
+    console.debug('开始请求');
+    const res = await this.ctx.curl(url, {
+      method: 'get',
+      headers: {
+        'content-type': 'application/json',
+      },
+      timeout: 6000,
+      dataType: 'json',
+    });
+    const { openid, unionid } = res.data;
+    if (!openid) throw new BusinessError(ErrorCode.BUSINESS, '未获取到openid', res.data);
+    return { openid, unionid };
+  }
+
+  // 发送模板消息,如果没有openid,查询一下是否
+  async sendTemplate(bodyData) {
+    let { openid, unionid, templateId, url, topColor = '#ff0000', data } = bodyData;
+    const { wx } = this.app.config;
+    let wxOpenid = '';
+    if (!openid) {
+      // 换公众号的openid
+      const wxres = await wx.getFollowers();
+      if (wxres.data) {
+        const wxusers = await wx.batchGetUsers(wxres.data.openid);
+        if (wxusers.user_info_list) {
+          for (let index = 0; index < wxusers.user_info_list.length; index++) {
+            const wxuser = wxusers.user_info_list[index];
+            if (wxuser.unionid === unionid) {
+              openid = wxuser.openid;
+              wxOpenid = wxuser.openid;
+              break;
+            }
+          }
+        }
+      }
+    }
+    let res = {};
+    // console.log(openid, templateId, url, topColor, data);
+    if (openid) {
+      res = await wx.sendTemplate(openid, templateId, url, topColor, data);
+    } else {
+      res.errcode = -1;
+      res.errmsg = '发送失败';
+      res.msgid = 0;
+    }
+    res.wxOpenid = wxOpenid;
+    return res;
+  }
+
+  // 微信关注缓存
+  async updateCache() {
+    if (!this.app.config.canUseMongoDb) {
+      const word = '!!!!!!!!!!mongodb连接测试失败,若需要使用mongodb数据库,需要重启项目进行检测!!!!!!!!!!';
+      this.ctx.logger.debug(word);
+      return word;
+    }
+    if (!this.userCache) {
+      this.ctx.app.logger.debug('!!!!!!!!!! 未找到 微信关注用户缓存 数据库链接,无法进行 微信关注用户缓存 !!!!!!!!!!');
+      this.ctx.ok('!!!!!!!!!! 未找到 微信关注用户缓存 数据库链接,无法进行 微信关注用户缓存 !!!!!!!!!!');
+    }
+    // 换取每次用户的数量,如果过大.可能会导致js缓存空间被撑死,程序挂掉,所以不用上限10000来搞;可以试着改大些,现在没有那么多关注
+    const batchLimit = 1000;
+    // 处理数量
+    let dealCount = 0;
+    // 关注总数
+    let wxTotal = 0;
+    // 下次开始位置
+    let next_openid = null;
+    do {
+      let arr;
+      try {
+        // 请求关注列表
+        const res = await this.wx.getFollowers(next_openid);
+        // 赋值,关注总数
+        if (wxTotal === 0) wxTotal = res.data;
+        // 赋值下次查询的位置
+        next_openid = res.next_openid;
+        // 统一接收请求结果的变量
+        const result = res.data;
+        if (result) {
+          // 取出openid列表
+          arr = _.get(result, 'openid', []);
+          arr = _.chunk(arr, batchLimit);
+          // 处理用户,记录数量
+          await this.dealUser(arr);
+        }
+      } catch (error) {
+        this.ctx.logger.debug(`微信公众号关注定期拉取:${moment().format('YYYY-MM-DD HH:mm:ss')},处理发生错误`);
+      }
+      // 拿出来是为了即使里面发生错误.也不会导致无限循环
+      dealCount += arr.length;
+      // 关注数大于处理数量就继续
+    } while (wxTotal > dealCount);
+    // 去匹配用户
+    await this.userMapping();
+  }
+  /**
+   * 请求每个openid的用户,创建/更新进缓存库
+   * @param {Array} arr openid列表
+   */
+  async dealUser(arr = []) {
+    for (const a of arr) {
+      const usersRes = await this.wx.batchGetUsers(a);
+      const userInfoList = _.get(usersRes, 'user_info_list', []);
+      for (const u of userInfoList) {
+        const { openid, unionid } = u;
+        const obj = { wxopenid: openid, unionid };
+        const count = await this.userCache.count({ unionid: obj.unionid });
+        if (count) {
+          await this.userCache.updateOne({ unionid: obj.unionid }, obj);
+        } else {
+          await this.userCache.create(obj);
+        }
+      }
+    }
+  }
+
+  /**
+   * 将没有wxopenid的用户拿出来匹配
+   */
+  async userMapping() {
+    let countTotal = 0;
+    let dealTotal = 0;
+    let skip = 0;
+    const limit = 10;
+    const wxUserCache = await this.userCache.find();
+    do {
+      const query = { skip, limit };
+      const res = await this.queryService.index({ table: 'security_guard_base' }, query);
+      const { data, total } = res;
+      if (countTotal === 0) countTotal = total;
+      for (const i of data) {
+        const { unionid, id } = i;
+        const r = wxUserCache.find(f => f.unionid === unionid);
+        if (!r) continue;
+        const { wxopenid } = r;
+        const q = { id };
+        const body = { gopenid: wxopenid };
+        this.updateService.index({ table: 'security_guard_base' }, q, body);
+      }
+      skip += limit;
+      dealTotal += data.length;
+    } while (countTotal > dealTotal);
+
+  }
+
+
+  async query() {
+    const data = await this.userCache.find();
+    const total = await this.userCache.count();
+    return { data, total };
+  }
+
+}
+
+module.exports = WxService;

+ 12 - 0
app/z_router/import.js

@@ -0,0 +1,12 @@
+'use strict';
+
+
+module.exports = app => {
+  const { router, controller } = app;
+  const profix = '/api/';
+  const target = 'import';
+  router.post(target, `${profix}${target}/examinee`, controller[target].examinee); // 考试相关
+  router.post(target, `${profix}${target}/staff`, controller[target].staff); // 保安员
+  router.post(target, `${profix}${target}/securityGuard`, controller[target].securityGuard); // 保安员
+  router.post(target, `${profix}${target}/guardWork`, controller[target].guardWork); // 保安员
+};

+ 10 - 0
app/z_router/login.js

@@ -0,0 +1,10 @@
+'use strict';
+
+
+module.exports = app => {
+  const { router, controller } = app;
+  const profix = '/api/';
+  const target = 'login';
+  router.post(target, `${profix}${target}/checkToken`, controller[target].checkToken); // 检查token
+  router.post(target, `${profix}${target}/:table`, controller[target].login); // 登陆
+};

+ 24 - 0
app/z_router/usual.js

@@ -0,0 +1,24 @@
+'use strict';
+
+
+module.exports = app => {
+  const { router, controller } = app;
+  const profix = '/api/';
+  const target = 'usual';
+  /**
+   * 处理保安员 户籍派出所编码 中间件;应用在 create和update上;
+   * 再添加到query上,查询需要做转换处理
+   */
+  router.get(target, `${profix}${target}/readMessage/:id`, controller[target].readMessage); // 确认消息
+  router.post(target, `${profix}${target}/sendMessage`, controller[target].sendMessage); // 发送消息
+  router.post(target, `${profix}${target}/reserve`, controller[target].reserve); // 采集信息预约
+  const housePoliceCode = app.middleware.securityGuard.housePoliceCode();
+  const levelSearch = app.middleware.levelSearch();
+  router.get(target, `${profix}${target}/:table/query`, housePoliceCode, levelSearch, controller[target].query); // 列表查询
+  router.get(target, `${profix}${target}/:table/fetch`, controller[target].fetch); // 单数据查询
+  router.post(target, `${profix}${target}/:table`, housePoliceCode, controller[target].create); // 创建
+  router.post(target, `${profix}${target}/:table/update`, housePoliceCode, controller[target].update); // 修改
+  router.delete(target, `${profix}${target}/:table`, controller[target].delete); // 删除
+  router.post(target, `${profix}${target}/:table/import`, controller[target].import); // 导入
+  router.get(target, `${profix}${target}/:table/statis`, controller[target].statis); // 统计
+};

+ 13 - 0
app/z_router/wx.js

@@ -0,0 +1,13 @@
+'use strict';
+
+
+module.exports = app => {
+  const { router, controller } = app;
+  const profix = '/api/';
+  const target = 'wx';
+  router.put(target, `${profix}${target}/userMapping`, controller[target].userMapping); // 匹配openid
+  router.put(target, `${profix}${target}/updateCache`, controller[target].updateCache); // 列表查询
+  router.get(target, `${profix}${target}`, controller[target].index); // 列表查询
+  router.get(target, `${profix}${target}/openid`, controller[target].getOpenid); // 列表查询
+  router.post(target, `${profix}${target}/sendTemplate`, controller[target].sendTemplate); // 模板消息
+};

+ 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

+ 101 - 0
config/config.default.js

@@ -0,0 +1,101 @@
+/* eslint valid-jsdoc: "off" */
+
+'use strict';
+
+/**
+ * @param {Egg.EggAppInfo} appInfo app info
+ */
+const { jwt } = require('./config.secret');
+const WechatAPI = require('co-wechat-api');
+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 + '_1629889590707_2425';
+
+  // add your middleware config here
+  config.middleware = [ 'tokenCheck', 'killColumns', 'operaLogs' ];
+
+  // add your user config here
+  const userConfig = {
+    // myAppName: 'egg',
+  };
+  config.cluster = {
+    listen: {
+      port: 6100,
+    },
+  };
+  config.jwt = {
+    ...jwt,
+    expiresIn: '1d',
+    issuer: 'baoan',
+  };
+  config.dbServerPort = '8090';
+  config.dbServerIp = 'baoan.fwedzgc.com'; // 106.12.161.200
+  config.wxServer = 'http://baoan.fwedzgc.com:8090';
+  config.export = {
+    root_path: 'D:\\temp\\',
+    domain: 'http://127.0.0.1',
+  };
+  config.import = {
+    domain: 'http://127.0.0.1',
+    root_path: 'D:\\temp\\',
+  };
+  config.wxConfig = {
+    appid: 'wxf7c766a58ace4bd6',
+    secret: '615597e3b2e36314740f8954aa53b10d',
+  };
+  config.wx = new WechatAPI(
+    config.wxConfig.appid,
+    config.wxConfig.secret
+  );
+  config.wxAppConfig = {
+    appid: 'wx68378872c2d354e9',
+    secret: '14298fa90cbfe41f7726afcb6097aca2',
+  };
+  config.wxApp = new WechatAPI(
+    config.wxAppConfig.appid,
+    config.wxAppConfig.secret
+  );
+  config.dbName = 'wxCache';
+  config.mongoose = {
+    url: `mongodb://localhost:27017/${config.dbName}`,
+    options: {
+      user: 'c##baoandba',
+      pass: 'baoan2021',
+      authSource: 'admin',
+      useNewUrlParser: true,
+      useCreateIndex: true,
+    },
+  };
+
+  // mq设置
+  // config.amqp = {
+  //   client: {
+  //     hostname: '127.0.0.1',
+  //     username: 'visit',
+  //     password: 'visit',
+  //     vhost: 'platform',
+  //   },
+  //   app: true,
+  //   agent: true,
+  // };
+  // redis设置
+  // config.redis = {
+  //   client: {
+  //     port: 6379, // Redis port
+  //     host: '127.0.0.1', // Redis host
+  //     password: '123456',
+  //     db: 1,
+  //   },
+  // };
+
+  return {
+    ...config,
+    ...userConfig,
+  };
+};

+ 20 - 0
config/config.prod.js

@@ -0,0 +1,20 @@
+'use strict';
+
+module.exports = () => {
+  const config = exports = {};
+  // config.logger = {
+  //   level: 'INFO',
+  //   allowDebugAtProd: true,
+  // };
+  config.dbServerPort = '6101';
+  config.dbServerIp = '127.0.0.1';
+  config.export = {
+    root_path: 'D:\\free\\workspace\\server\\service-file\\upload\\',
+    domain: 'http://127.0.0.1',
+  };
+  config.import = {
+    domain: 'http://127.0.0.1',
+    root_path: 'D:\\free\\workspace\\server\\service-file\\upload\\',
+  };
+  return config;
+};

+ 7 - 0
config/config.secret.js

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

+ 9 - 0
config/plugin.js

@@ -0,0 +1,9 @@
+'use strict';
+
+/** @type Egg.EggPlugin */
+module.exports = {
+  // had enabled by egg
+  // static: {
+  //   enable: true,
+  // }
+};

+ 17 - 0
ecosystem.config.js

@@ -0,0 +1,17 @@
+'use strict';
+
+const app = 'service-baoan';
+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": [
+    "**/*"
+  ]
+}

+ 54 - 0
package.json

@@ -0,0 +1,54 @@
+{
+  "name": "server-baoan",
+  "version": "1.0.0",
+  "description": "保安服务端",
+  "private": true,
+  "egg": {
+    "framework": "naf-framework-mongoose-free"
+  },
+  "dependencies": {
+    "co-wechat-api": "^3.11.0",
+    "egg": "^2.15.1",
+    "egg-naf-amqp": "0.0.13",
+    "egg-redis": "^2.4.0",
+    "egg-scripts": "^2.11.0",
+    "exceljs": "^4.2.0",
+    "lodash": "^4.17.15",
+    "moment": "^2.24.0",
+    "naf-framework-mongoose-free": "0.0.2"
+  },
+  "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",
+    "jsonwebtoken": "^8.5.1"
+  },
+  "engines": {
+    "node": ">=10.0.0"
+  },
+  "scripts": {
+    "start": "egg-scripts start --title=egg-server-server-baoan",
+    "stop": "egg-scripts stop --title=egg-server-server-baoan",
+    "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"
+}

+ 9 - 0
server.js

@@ -0,0 +1,9 @@
+
+// eslint-disable-next-line strict
+const egg = require('egg');
+const line = 1;// Number(process.argv[2] || require('os').cpus().length);
+const workers = line;
+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);
+  });
+});

+ 10 - 0
test/test.js

@@ -0,0 +1,10 @@
+'use strict';
+
+const { app, assert } = require('egg-mock/bootstrap');
+const _ = require('lodash');
+const moment = require('moment');
+describe('test/test.js', () => {
+  const time = '2021-11-25T08:44:06.530Z';
+  const nt = moment(time).format('YYYY-MM-DD HH:mm:ss');
+  console.log(nt);
+});

+ 3 - 0
todo.md

@@ -0,0 +1,3 @@
+# 备忘录,不一定是急着做的
+
+* 

+ 39 - 0
目录结构.md

@@ -0,0 +1,39 @@
+# 目录结构
+### 前端如果是VUE,项目就是应该是MVVC模式;
+###  如果是H5,项目就应该是MVC;
+###  反正服务端最主要的就是M+C部分
+## 1.app(按实际app目录介绍,非重要顺序)
+    项目代码基本都在这里,此文件夹外的文件多数是设置
+
+### 1.1 controller 
+    和java各种框架差不多,controller处理非数据交互部分,不过说实话,我自己写的时候,分的不清.
+
+### 1.2 middleware 
+    中间件,目前多用于针对已完成接口的修改,有大有小的修改要求,不过都是基于原处理基础上进行修改的要求.
+    例如:在查询保安时,需要企业名称,但是只有企业id. 不需要用企业id再进行一个/一个个查,而是通过中间件,拦截已查询的结果,再查询整理作为接口最后的返回. 不过这里的整理部分基本相似,可以抽出工具函数,现在没抽.
+
+### 1.3 model (不用了,转移至springBoot,Magic-api进行数据操作)
+    如其名,写实体的地方,在service和controller中都可以使用:
+    this.ctx.model.Xxx(注意首字母是大写,如果有 `_` 则需要转换成大驼峰),可以多层级,文件夹继续 `·` 即可:
+    this.ctx.model.Aaa.Bbb
+    model使用中,首字母都需要大写
+    写错会导致的情况多是 数据库插件的函数 undefined,例如:
+    function xxx(find) is undefined
+
+### 1.4 public 
+    可以放文件.
+    1.query.js:查询的设置,针对表,详情去文件里看
+    2.dataToDb.js: 前端数据 进入 数据库 相关js.有些前端的值 不能/不适合 直接应用在数据库层操作,所以使用这个文件配置.
+    3.dbToData.js: 数据库数据 传到 前端 相关js.有些值给前端,也需要转换,直接在这里配置好,就可以不需要转换了,主要是这个规则是服务端定的.前端整就多余了.自己整的活自己干
+
+### 1.5 service
+    处理数据层面,大部分东西都写这里,因为用董哥的mongodb框架习惯了,秉承:能自动绝不手写的原则.不过咱自己还没抽出不带数据库 或者 任意数据库的框架,这次可能要手写了,不过可以把工具类拿出来,自动输出 controller,真是有点懒得写那么多.先这么麻烦写吧
+
+### 1.6 z_router
+    Q:为什么+z?
+    A:因为不+z,router文件夹就会排到service上面,但实际上,这东西动的相对较少.插中间时,在controller和service model来回切换的情况时,它非常 非常 非常碍事,所以我直接送它个z.
+    实际上命名成什么都行
+    这个文件夹主要是放路由文件的
+    eggjs中,路由文件的作用是局部中间件的使用,及对应controller(这个都一样).如果是全局中间件,在config中设置
+### 1.7 router.js
+    它才是eggjs框架会读取的路由文件,z_router写完的,都要在router.js中引入,才好使,否则404

+ 17 - 0
码代码注意.md

@@ -0,0 +1,17 @@
+# 写代码注意事项 ($\color{red}{随时补充}$)
+* 这个命名挺恶心,我也没太整明白,只要有符号(多数使用下划线).  
+  在model时,要用 $\color{#4285f4}{大驼峰}$ ;其他时候用 $\color{#4285f4}{小驼峰}$
+
+* 语法基本都是ES,没啥别的.TS没敢用,TS的话还是换框架好,eggjs支持不好
+
+* 特殊处理符号: 
+  指的是需要对带有符号的变量key做 $\color{red}{处理}$ 后可以 在 $\color{red}{数据库}$ 中进行操作的特殊符号.目前有:
+  |符号|说明|
+  |:-:|:-|
+  |@|范围查询符号: `${key}@start/end` 为使用方式,会砍掉 start/end|
+
+
+  $\color{yellow}{特殊符号及公共处理函数已经整合到service/special.js下,有注释,应该没什么太多问题吧?!!}$
+  
+
+