lrf 1 year ago
commit
79fb6ac460

+ 29 - 0
.autod.conf.js

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

+ 1 - 0
.eslintignore

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

+ 3 - 0
.eslintrc

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

+ 14 - 0
.gitignore

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

+ 33 - 0
README.md

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

+ 14 - 0
app.js

@@ -0,0 +1,14 @@
+'use strict';
+class AppBootHook {
+  constructor(app) {
+    this.app = app;
+  }
+
+  async serverDidReady() {
+    // 所有的插件都已启动完毕,但是应用整体还未 ready
+    // 可以做一些数据初始化等操作,这些操作成功才会启动应用
+    const ctx = await this.app.createAnonymousContext();
+    await ctx.service.card.login();
+  }
+}
+module.exports = AppBootHook;

+ 39 - 0
app/controller/.card.js

@@ -0,0 +1,39 @@
+module.exports = {
+  create: {
+    requestBody: [],
+  },
+  destroy: {
+    params: ['!id'],
+    service: 'delete',
+  },
+  update: {
+    params: ['!id'],
+    requestBody: [],
+  },
+  show: {
+    parameters: {
+      params: ['!id'],
+    },
+    service: 'fetch',
+  },
+  index: {
+    parameters: {
+      query: {
+        'meta.createdAt@start': 'meta.createdAt@start',
+        'meta.createdAt@end': 'meta.createdAt@end',
+      },
+      // options: {
+      //   "meta.state": 0 // 默认条件
+      // },
+    },
+    service: 'query',
+    options: {
+      query: ['skip', 'limit'],
+      sort: ['meta.createdAt'],
+      desc: true,
+      count: true,
+    },
+  },
+  loop: {},
+  login: {},
+};

+ 62 - 0
app/controller/.position.js

@@ -0,0 +1,62 @@
+module.exports = {
+  create: {
+    requestBody: ['!user_id', '!company_id', '!company_name', '!longitude', '!latitude', '!name', '!card', '!is_class', 'company_service_object_name', 'company_service_object_id'],
+  },
+  destroy: {
+    params: ["!id"],
+    service: "delete",
+  },
+  // 没有修改
+  // update: {
+  //   params: ["!id"],
+  //   requestBody: [],
+  // },
+  show: {
+    parameters: {
+      params: ['!id'],
+    },
+    service: 'fetch',
+  },
+  index: {
+    parameters: {
+      query: {
+        user_id: 'user_id',
+        company_id: 'company_id',
+        name: 'name',
+        card: 'card',
+        is_class: 'is_class',
+        company_service_object_name: 'company_service_object_name',
+        company_service_object_id: 'company_service_object_id',
+        'time@start': 'meta.createdAt@start',
+        'time@end': 'meta.createdAt@end',
+      },
+      // options: {
+      //   "meta.state": 0 // 默认条件
+      // },
+    },
+    service: 'query',
+    options: {
+      query: ['skip', 'limit'],
+      sort: ['meta.createdAt'],
+      asc: true,
+      count: true,
+      // projection: { name: 1, longitude: 1, latitude: 1, is_class: 1, meta: 1 },
+    },
+  },
+  map: {
+    parameters: {
+      query: {
+        user_id: 'user_id',
+        company_id: 'company_id',
+        company_name: 'company_name',
+        name: 'name',
+        card: 'card',
+        is_class: 'is_class',
+        company_service_object_name: 'company_service_object_name',
+        company_service_object_id: 'company_service_object_id',
+        'time@start': 'meta.createdAt@start',
+        'time@end': 'meta.createdAt@end',
+      },
+    },
+  },
+};

+ 13 - 0
app/controller/card.js

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

+ 17 - 0
app/controller/home.js

@@ -0,0 +1,17 @@
+'use strict';
+
+const Controller = require('egg').Controller;
+const { BusinessError, ErrorCode } = require('naf-core').Error;
+
+class HomeController extends Controller {
+  async index() {
+    const { ctx } = this;
+    ctx.body = 'hi, egg';
+  }
+  async util() {
+    const res = await this.ctx.model.Position.deleteMany();
+    this.ctx.ok(res);
+  }
+}
+
+module.exports = HomeController;

+ 13 - 0
app/controller/position.js

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

+ 18 - 0
app/model/cardAdmin.js

@@ -0,0 +1,18 @@
+'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 cardAdmin = {
+  uid: { type: String }, // 工牌账号id
+  uKey: { type: String }, // 工牌账号授权码
+  remark: { type: String },
+};
+const schema = new Schema(cardAdmin, { toJSON: { virtuals: true } });
+schema.index({ 'meta.createdAt': 1 });
+schema.plugin(metaPlugin);
+module.exports = app => {
+  const { mongoose } = app;
+  return mongoose.model('CardAdmin', schema, 'card_admin');
+};

+ 22 - 0
app/model/cardCache.js

@@ -0,0 +1,22 @@
+'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 cardCache = {
+  vid: { type: String }, // 工牌id
+  vKey: { type: String }, // 工牌授权码
+  work_card: { type: String }, // 工牌id
+  remark: { type: String },
+};
+const schema = new Schema(cardCache, { toJSON: { virtuals: true } });
+schema.index({ id: 1 });
+schema.index({ vid: 1 });
+schema.index({ work_card: 1 });
+schema.index({ 'meta.createdAt': 1 });
+schema.plugin(metaPlugin);
+module.exports = app => {
+  const { mongoose } = app;
+  return mongoose.model('CardCache', schema, 'card_cache');
+};

+ 36 - 0
app/model/position.js

@@ -0,0 +1,36 @@
+'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 position = {
+  user_id: { type: String },
+  company_id: { type: String }, // 企业id
+  company_name: { type: String }, // 企业名
+  longitude: { type: String }, // 经度
+  latitude: { type: String }, // 纬度
+  name: { type: String }, // 姓名
+  card: { type: String }, // 身份证号
+  is_class: { type: String }, // 是否在岗: 在岗/下班
+  company_service_object_id: { type: String }, // 服务对象id
+  company_service_object_name: { type: String }, // 服务对象
+  info: { type: Object }, // 工牌定位额外信息
+  remark: { type: String },
+};
+const schema = new Schema(position, { toJSON: { virtuals: true } });
+schema.index({ id: 1 });
+schema.index({ user_id: 1 });
+schema.index({ company_id: 1 });
+schema.index({ company_name: 1 });
+schema.index({ name: 1 });
+schema.index({ card: 1 });
+schema.index({ is_class: 1 });
+schema.index({ company_service_object_id: 1 });
+schema.index({ company_service_object_name: 1 });
+schema.index({ 'meta.createdAt': 1 });
+schema.plugin(metaPlugin);
+module.exports = app => {
+  const { mongoose } = app;
+  return mongoose.model('Position', schema, 'position');
+};

+ 22 - 0
app/router.js

@@ -0,0 +1,22 @@
+'use strict';
+
+/**
+ * @param {Egg.Application} app - egg application
+ */
+module.exports = app => {
+  const { router, controller } = app;
+  router.get('/', controller.home.index);
+  router.post('/api/position/util', controller.home.util);
+
+  // 定位
+  router.post('/api/position', controller.position.create);
+  router.get('/api/position', controller.position.index);
+  router.get('/api/position/map', controller.position.map);
+  router.get('/api/position/:id', controller.position.show);
+  router.delete('/api/position/:id', controller.position.destroy);
+  // 定位工牌
+  // router.post('/api/position/card', controller.card.create);
+  router.get('/api/position/card', controller.card.index);
+  // router.get('/api/position/card/loop', controller.card.loop);
+  // router.post('/api/position/card/login', controller.card.login);
+};

+ 10 - 0
app/schedule/position.js

@@ -0,0 +1,10 @@
+'use strict';
+module.exports = {
+  schedule: {
+    interval: '10s', // 1 分钟间隔
+    type: 'worker', // 指定所有的 worker 都需要执行
+  },
+  async task(ctx) {
+    await ctx.service.card.loop();
+  },
+};

+ 292 - 0
app/service/card.js

@@ -0,0 +1,292 @@
+'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 axios = require('axios');
+const crypto = require('crypto');
+// 定位卡
+class CardService extends CrudService {
+  constructor(ctx) {
+    super(ctx, 'card');
+    this.model = this.ctx.model.Position;
+    this.cardCacheModel = this.ctx.model.CardCache;
+    this.cardAdminModel = this.ctx.model.CardAdmin;
+    this.tp = this.ctx.service.transformPosition;
+    this.dbConfig = this.app.config.dbServer;
+  }
+
+  /**
+   * 定时任务执行函数,login是在应用加载完成时开始的.
+   * 登陆过期时间是12h,请求更新token时间,所以应该不用担心会有token过期问题
+   */
+  async loop() {
+    // 查询 vid
+    const { ip, port } = this.dbConfig;
+    const query = [
+      { key: 'is_class', value: '在岗', type: 'like' },
+      { key: 'work_card', type: 'exists' }, // 工牌字段
+    ];
+    // 直接把在岗且有工牌的人查出来,然后去处理后面的 定位获取 及 定位保存
+    const res = await axios({
+      method: 'post',
+      url: `http://${ip}:${port}/db/query?table=security_guard_base`,
+      data: query,
+      responseType: 'json',
+    });
+    const data = this.selfRequestDeal(res);
+    // 正在上班的保安列表
+    const isWorkingList = data.list;
+    // 分散开处理,不需要await进行等待,各玩各的
+    for (const worker of isWorkingList) {
+      this.dealPosition(worker);
+    }
+    // 入职,个人信息,服务对象
+  }
+
+  /**
+   * 组织每个保安员的定位信息 及 信息保存
+   * @param {Object} worker 符合条件的保安员基础信息(security_guard_base)
+   */
+  async dealPosition(worker) {
+    if (!worker) return;
+    let positionData = {};
+    const { id, work_card } = worker;
+    positionData = _.pick(worker, [ 'name', 'card', 'is_class' ]);
+    positionData.user_id = id;
+    // 获取入职信息 company_baoan_work
+    const cbw = await this.getBaoanWork(id);
+    if (cbw) {
+      positionData.company_name = _.get(cbw, 'company_name');
+      positionData.company_id = _.get(cbw, 'company_id');
+    }
+    // 获取服务对象信息 company_baoan_dispatch
+    const dispatch = await this.getBaoanDispatch(id);
+    if (dispatch) {
+      positionData.company_service_object_id = _.get(dispatch, 'service_target_id');
+      positionData.company_service_object_name = _.get(dispatch, 'service_target');
+    }
+    // 获取定位信息
+    const position = await this.getPosition(work_card);
+    if (position) {
+      const { latitude, longitude, ...info } = position;
+      positionData = { ...positionData, latitude, longitude, info };
+      this.model.create(positionData);
+    }
+  }
+
+  /**
+   * 获取该工牌实时定位信息
+   * @param {String} vid 工牌id
+   */
+  async getPosition(vid) {
+    if (!vid) return;
+    let vObj = await this.getAuthorizeCode(vid);
+    // 有vKey的话,就试试好不好使
+    // 第一次可能是缓存,也可能是请求,不确定;所以第一次如果没有返回
+    // 则接下来需要禁用缓存再来一遍,如果还是不行,那就没辙了
+    if (vObj) {
+      const position = await this.toGetPosition(vObj.vKey, vObj.vid);
+      if (position) return position;
+    }
+    vObj = null;
+    // 第二次了,不使用缓存了,第一次要是有就结束了,不会来这了
+    vObj = await this.getAuthorizeCode(vid, false);
+    if (vObj) {
+      const position = await this.toGetPosition(vObj.vKey, vObj.vid);
+      if (position) return position;
+    }
+  }
+
+  /**
+   * 请求车辆最新信息并处理
+   * @param {String} vKey 工牌授权码
+   * @param {String} vid 工牌id
+   */
+  async toGetPosition(vKey, vid) {
+    // 查询定位点
+    const method = 'loadLocation';
+    const data = await this.toRequest(method, { vid, vKey });
+    const position = this.getPositionFromData(data);
+    if (position) {
+      const obj = { info: {} };
+      if (_.get(position, 'lat')) obj.latitude = _.get(position, 'lat');
+      if (_.get(position, 'lng')) obj.longitude = _.get(position, 'lng');
+      if (obj.latitude && obj.longitude) {
+        const [ latitude, longitude ] = this.tp.transform(obj.latitude, obj.longitude);
+        if (latitude && longitude) {
+          obj.latitude = latitude;
+          obj.longitude = longitude;
+        }
+      }
+      // 额外内容
+      if (_.get(position, 'info')) obj.info = _.get(position, 'info'); // 文字位置信息
+      if (_.get(position, 'state')) obj.state = _.get(position, 'state'); // 工牌状态
+      if (_.get(position, 'speed')) obj.speed = _.get(position, 'speed'); // 速度
+      if (_.get(position, 'direct')) obj.direct = _.get(position, 'direct'); // 方向
+      if (_.get(position, 'vhcofflinemin')) obj.vhcofflinemin = _.get(position, 'vhcofflinemin'); // 不在线时长(分钟)
+      if (Object.keys(obj).length > 0) return obj;
+    }
+  }
+
+  /**
+   * 处理最新定位信息接口返回结果
+   * @param {Object} data 请求最新定位信息接口返回结果
+   */
+  getPositionFromData(data) {
+    if (data) {
+      const { locs } = data;
+      if (locs && _.isArray(locs)) {
+        const l = _.head(locs);
+        if (l) return l;
+      }
+    }
+  }
+
+  /**
+   * 获取工牌授权码
+   * @param {String} work_card 工牌id
+   * @param {Boolean} useCache 是否使用缓存
+   */
+  async getAuthorizeCode(work_card, useCache = true) {
+    if (!work_card) return;
+    let data;
+    // 先在缓存中找下
+    if (useCache) {
+      data = await this.cardCacheModel.findOne({ work_card });
+      if (data) return data;
+    }
+    // 如果缓存里没有,从接口走一遍再找
+    const list = await this.toCacheCardGroup();
+    const r = list.find(f => `${f.work_card}` === `${work_card}`);
+    // 再没有就GG
+    if (r) return r;
+  }
+
+  /**
+   * 缓存并返回获取的车辆数据
+   */
+  async toCacheCardGroup() {
+    const users = await this.cardAdminModel.find({}).limit(1);
+    if (users.length <= 0) return;
+    const user = _.head(users);
+    const data = await this.toRequest('loadVehicles', { uid: user.uid, uKey: user.uKey });
+    let list = _.get(data, 'groups', []);
+    list = _.flattenDeep(list.map(i => i.vehicles));
+    list = list.map(i => ({ vid: i.id, vKey: i.vKey, work_card: i.name }));
+    // 缓存
+    await this.cardCacheModel.deleteMany();
+    await this.cardCacheModel.insertMany(list);
+    // 返回
+    return list;
+  }
+
+  /**
+   * 获取保安员入职信息
+   * @param {String} security_guard_id 保安员id
+   */
+  async getBaoanWork(security_guard_id) {
+    const { ip, port } = this.dbConfig;
+    const res = await axios({
+      method: 'post',
+      url: `http://${ip}:${port}/db/fetch?table=company_baoan_work`,
+      data: { security_guard_id, is_quit: '0' },
+    });
+    const data = this.selfRequestDeal(res);
+    return data;
+  }
+
+  /**
+   * 获取保安员派遣信息
+   * @param {String} security_guard_id 保安员id
+   */
+  async getBaoanDispatch(security_guard_id) {
+    const { ip, port } = this.dbConfig;
+    const res = await axios({
+      method: 'post',
+      url: `http://${ip}:${port}/db/fetch?table=company_baoan_dispatch`,
+      data: { security_guard_id, status: '0' },
+    });
+    const data = this.selfRequestDeal(res);
+    return data;
+  }
+
+  /**
+   * 登陆;用户放在session中
+   */
+  async login() {
+    console.log('初始化----------工牌接口登陆');
+    const name = 'fwedz';
+    const pwd = '000000';
+    const method = 'loginSystem';
+    const res = await this.toRequest(method, { name, pwd });
+    if (res.success) {
+      await this.cardAdminModel.deleteMany();
+      await this.cardAdminModel.create(_.pick(res, [ 'uid', 'uKey' ]));
+      // 不能存在session中,不共享,存在库里吧
+      // await this.cardAdminModel.findOne() = _.pick(res, [ 'uid', 'uKey' ]);
+    }
+  }
+
+  /**
+   * 请求接口
+   * @param {String} method 函数
+   * @param {Object} params 参数
+   */
+  async toRequest(method, params = {}) {
+    if (!method) return;
+    let url = `http://47.104.0.190:89/gpsonline/GPSAPI?version=1&method=${method}`;
+    for (const key in params) {
+      url += `&${key}=${params[key]}`;
+    }
+    const req = await axios.get(url);
+    const data = this.requestReturnDeal(req);
+    return data;
+  }
+
+  /**
+   * 错误码文案
+   * @param {Number} code 错误码
+   */
+  errorType(code) {
+    const object = {
+      0: '系统出错',
+      1: '方法名不存在',
+      2: '版本号不存在',
+      3: '参数不能为空',
+      4: '参数值格式有误',
+      5: '用户名或密码错误',
+      6: '授权码无效',
+      7: '超过一小时最大访问次数',
+    };
+
+    return object[code];
+  }
+  /**
+   * 请求结果处理
+   * @param {Object} req 统一处理请求
+   */
+  requestReturnDeal(req) {
+    if (req.status !== 200) {
+      console.error('接口请求失败----http失败');
+      return;
+    }
+    if (req.data) return req.data;
+  }
+
+  /**
+   * 保安系统接口处理
+   * @param {Object} req 统一处理请求
+   */
+  selfRequestDeal(req) {
+    if (req.status !== 200) {
+      console.error('接口请求失败----http失败');
+      return;
+    }
+    const { data } = req.data;
+    if (data) return data;
+  }
+
+}
+
+module.exports = CardService;

+ 93 - 0
app/service/position.js

@@ -0,0 +1,93 @@
+'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 PositionService extends CrudService {
+  constructor(ctx) {
+    super(ctx, 'position');
+    this.model = this.ctx.model.Position;
+    this.util = this.ctx.service.util;
+  }
+
+  // async query(filter, { skip, limit, sort, desc, projection } = {}) {
+  //   // 处理排序
+  //   if (sort && _.isString(sort)) {
+  //     sort = { [sort]: desc ? -1 : 1 };
+  //   } else if (sort && _.isArray(sort)) {
+  //     sort = sort.map(f => ({ [f]: desc ? -1 : 1 })).reduce((p, c) => ({ ...p, ...c }), {});
+  //   }
+  //   let condition = _.cloneDeep(filter);
+  //   condition = this.util.dealFilter(condition);
+  //   const pipeline = [{ $match: condition }];
+  //   // 先排序
+  //   if (sort) pipeline.push({ $sort: sort });
+  //   // 再进行分页
+  //   if (skip && limit) {
+  //     pipeline.push({ $skip: parseInt(skip) });
+  //     pipeline.push({ $limit: parseInt(limit) });
+  //   }
+  //   // 再将数据过滤
+  //   if (projection) pipeline.push({ $project: projection });
+  //   // console.log(JSON.stringify(pipeline));
+  //   // const query = [
+  //   //   { $addFields: { time: '$meta.createdAt' } },
+  //   //   { $match: { time: { $gte: new Date('2021-12-03 12:45:00'), $lte: new Date('2021-12-03 12:45:08') } } },
+  //   //   { $skip: 0 },
+  //   //   { $limit: 1 },
+  //   // ];
+  //   const rs = await this.model.aggregate(pipeline);
+  //   // const rs = await this.model.find(trimData(condition), projection, { skip, limit, sort }).exec();
+  //   for (const i of rs) {
+  //     const { time } = i;
+  //     console.log(typeof time);
+  //   }
+  //   return rs;
+  // }
+
+  async map(condition = {}) {
+    // 当前时间
+    const format = 'YYYY-MM-DD HH:mm:ss';
+    const end = moment().format(format);
+    const start = moment(end).subtract(2, 'm').format(format);
+    condition['meta.createdAt@start'] = start;
+    condition['meta.createdAt@end'] = end;
+    condition = this.util.dealQuery(condition);
+    const data = await this.model.aggregate([
+      { $sort: { 'meta.createdAt': -1 } }, // 倒序排序
+      { $match: { ...condition } },
+      // 只取出第一个数据
+      {
+        $group: {
+          _id: '$user_id',
+          data_id: { $first: '$_id' },
+          user_id: { $first: '$user_id' },
+          company_id: { $first: '$company_id' },
+          company_name: { $first: '$company_name' },
+          company_service_object_id: { $first: '$company_service_object_id' },
+          company_service_object_name: { $first: '$company_service_object_name' },
+          longitude: { $first: '$longitude' },
+          latitude: { $first: '$latitude' },
+          name: { $first: '$name' },
+          card: { $first: '$card' },
+          is_class: { $first: '$is_class' },
+        },
+      },
+    ]);
+    return data;
+  }
+
+  async delete({ id }) {
+    console.log(id);
+    const req = this.ctx.request;
+    const dkey = _.get(req, 'header.dkey');
+    if (dkey !== 'free') {
+      throw new BusinessError(ErrorCode.SERVICE_FAULT, '您谁,不认识,这边不行,要不您去数据库删吧!');
+    }
+    return this.model.deleteOne({ _id: id });
+  }
+}
+
+module.exports = PositionService;

+ 55 - 0
app/service/transform_position.js

@@ -0,0 +1,55 @@
+'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 Transform_positionService extends CrudService {
+  constructor(ctx) {
+    super(ctx, 'transform_position');
+    this.a = 6378245.0;
+    this.ee = 0.00669342162296594323;
+  }
+
+  transform(wgLat, wgLon) {
+    if (this.outOfChina(wgLat, wgLon)) {
+      return [ wgLat, wgLon ];
+    }
+    let dLat = this.transformLat(wgLon - 105.0, wgLat - 35.0);
+    let dLon = this.transformLon(wgLon - 105.0, wgLat - 35.0);
+    const radLat = (wgLat / 180.0) * Math.PI;
+    let magic = Math.sin(radLat);
+    magic = 1 - this.ee * magic * magic;
+    const sqrtMagic = Math.sqrt(magic);
+    dLat = (dLat * 180.0) / (((this.a * (1 - this.ee)) / (magic * sqrtMagic)) * Math.PI);
+    dLon = (dLon * 180.0) / ((this.a / sqrtMagic) * Math.cos(radLat) * Math.PI);
+    const mgLat = wgLat + dLat;
+    const mgLon = wgLon + dLon;
+
+    return [ mgLat, mgLon ];
+  }
+
+  outOfChina(lat, lon) {
+    if (lon < 72.004 || lon > 137.8347) return true;
+    if (lat < 0.8293 || lat > 55.8271) return true;
+    return false;
+  }
+
+  transformLat(x, y) {
+    let ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x));
+    ret += ((20.0 * Math.sin(6.0 * x * Math.PI) + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0) / 3.0;
+    ret += ((20.0 * Math.sin(y * Math.PI) + 40.0 * Math.sin((y / 3.0) * Math.PI)) * 2.0) / 3.0;
+    ret += ((160.0 * Math.sin((y / 12.0) * Math.PI) + 320 * Math.sin((y * Math.PI) / 30.0)) * 2.0) / 3.0;
+    return ret;
+  }
+  transformLon(x, y) {
+    let ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x));
+    ret += ((20.0 * Math.sin(6.0 * x * Math.PI) + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0) / 3.0;
+    ret += ((20.0 * Math.sin(x * Math.PI) + 40.0 * Math.sin((x / 3.0) * Math.PI)) * 2.0) / 3.0;
+    ret += ((150.0 * Math.sin((x / 12.0) * Math.PI) + 300.0 * Math.sin((x / 30.0) * Math.PI)) * 2.0) / 3.0;
+    return ret;
+  }
+}
+
+module.exports = Transform_positionService;

+ 76 - 0
app/service/util.js

@@ -0,0 +1,76 @@
+'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) {
+  }
+
+
+  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);
+    const times = [];
+    for (const k of keys) {
+      if (k.includes('@')) {
+        const karr = k.split('@');
+        if (karr.length === 2) {
+          const prefix = karr[0];
+          const type = karr[1];
+          if (type === 'start') {
+            if (filter[k] && filter[k] !== '') {
+              const obj = { key: prefix, opera: '$gte', value: new Date(filter[k]) };
+              times.push(obj);
+            }
+          } else {
+            if (filter[k] && filter[k] !== '') {
+              const obj = { key: prefix, opera: '$lte', value: new Date(filter[k]) };
+              times.push(obj);
+            }
+          }
+          delete filter[k];
+        }
+      }
+    }
+    for (const i of times) {
+      const { key, opera, value } = i;
+      if (!filter[key]) {
+        filter[key] = {};
+      }
+      filter[key][opera] = value;
+    }
+    return filter;
+  }
+
+}
+module.exports = UtilService;

+ 65 - 0
config/config.default.js

@@ -0,0 +1,65 @@
+/* eslint valid-jsdoc: "off" */
+
+'use strict';
+
+const { jwt } = require('./config.secret');
+/**
+ * @param {Egg.EggAppInfo} appInfo app info
+ */
+module.exports = appInfo => {
+  /**
+   * built-in config
+   * @type {Egg.EggAppConfig}
+   **/
+  const config = exports = {};
+
+  // use for cookie sign key, should change to your own and keep security
+  config.keys = appInfo.name + '_1632050612908_5301';
+
+  // add your middleware config here
+  config.middleware = [];
+
+  // add your user config here
+  const userConfig = {
+    // myAppName: 'egg',
+  };
+
+  config.cluster = {
+    listen: {
+      port: 6102,
+    },
+  };
+
+  config.dbName = 'baoan-position';
+  config.mongoose = {
+    url: `mongodb://localhost:27017/${config.dbName}`,
+    options: {
+      user: 'c##baoandba',
+      pass: 'baoan2021',
+      authSource: 'admin',
+      useNewUrlParser: true,
+      useCreateIndex: true,
+    },
+  };
+
+  config.map = {
+    key: '322f362741dd84dd2c3f1d03825d9371',
+    type: 'gps',
+  };
+
+  config.jwt = {
+    ...jwt,
+    expiresIn: '1d',
+    issuer: 'baoan',
+  };
+  // 服务设置
+  config.dbServer = {
+    ip: '106.12.161.200',
+    port: '6101',
+  };
+
+  return {
+    ...config,
+    ...userConfig,
+  };
+};

+ 21 - 0
config/config.prod.js

@@ -0,0 +1,21 @@
+'use strict';
+
+module.exports = () => {
+  const config = exports = {};
+  config.dbServer = {
+    ip: '127.0.0.1',
+    port: '6101',
+  };
+  config.dbName = 'baoan-position';
+  config.mongoose = {
+    url: `mongodb://localhost:27017/${config.dbName}`,
+    options: {
+      user: 'c##baoandba',
+      pass: 'baoan2021',
+      authSource: 'admin',
+      useNewUrlParser: true,
+      useCreateIndex: true,
+    },
+  };
+  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-position';
+module.exports = {
+  apps: [{
+    name: app, // 应用名称
+    script: './server.js', // 实际启动脚本
+    out: `./logs/${app}.log`,
+    error: `./logs/${app}.err`,
+    watch: [ // 监控变化的目录,一旦变化,自动重启
+      'app', 'config',
+    ],
+    env: {
+      NODE_ENV: 'production', // 环境参数,当前指定为生产环境
+    },
+  }],
+};

+ 53 - 0
package.json

@@ -0,0 +1,53 @@
+{
+  "name": "server-position",
+  "version": "1.0.0",
+  "description": "定位存储服务",
+  "private": true,
+  "egg": {
+    "framework": "naf-framework-mongoose-free"
+  },
+  "dependencies": {
+    "axios": "^0.24.0",
+    "egg": "^2.15.1",
+    "egg-naf-amqp": "0.0.13",
+    "egg-redis": "^2.4.0",
+    "egg-scripts": "^2.11.0",
+    "lodash": "^4.17.21",
+    "moment": "^2.29.1",
+    "naf-framework-mongoose-free": "^0.0.10"
+  },
+  "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 --daemon --title=egg-server-service-position",
+    "stop": "egg-scripts stop --title=egg-server-service-position",
+    "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 workers = Number(process.argv[2] || require('os').cpus().length);
+egg.startCluster({
+  workers,
+  baseDir: __dirname,
+});

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

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