wangzhibo
昨天 6484c72be4230e89ab45dc928250de9d5f3326ca
add maps
3个文件已添加
371 ■■■■■ 已修改文件
src/modules/maps/config.ts 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/maps/controller/admin/live.ts 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/maps/service/live.ts 293 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/maps/config.ts
New file
@@ -0,0 +1,14 @@
import { ModuleConfig } from '@cool-midway/core';
/**
 * 地图监控模块
 */
export default () => {
  return {
    name: '地图监控',
    description: '站点、分拣员、车辆实时位置与轨迹',
    middlewares: [],
    globalMiddlewares: [],
    order: 0,
  } as ModuleConfig;
};
src/modules/maps/controller/admin/live.ts
New file
@@ -0,0 +1,64 @@
import { Body, Inject, Post } from '@midwayjs/core';
import { CoolController, BaseController } from '@cool-midway/core';
import { MapsLiveService } from '../../service/live';
/**
 * 地图实时位置与轨迹
 */
@CoolController()
export class AdminMapsLiveController extends BaseController {
  @Inject()
  mapsLiveService: MapsLiveService;
  @Post('/sites', { summary: '站点地图点位' })
  async sites() {
    return this.ok(await this.mapsLiveService.sites());
  }
  @Post('/siteWeight', { summary: '站点称重历史' })
  async siteWeight(
    @Body('businessId') businessId: string,
    @Body('startTime') startTime: string,
    @Body('endTime') endTime: string
  ) {
    return this.ok(await this.mapsLiveService.siteWeight(businessId, startTime, endTime));
  }
  @Post('/sorterOnline', { summary: '在线分拣员位置' })
  async sorterOnline() {
    return this.ok(await this.mapsLiveService.sorterOnline());
  }
  @Post('/sorterTrack', { summary: '分拣员历史轨迹' })
  async sorterTrack(
    @Body('idCard') idCard: string,
    @Body('startTime') startTime: string,
    @Body('endTime') endTime: string
  ) {
    return this.ok(await this.mapsLiveService.sorterTrack(idCard, startTime, endTime));
  }
  @Post('/sorterHealth', { summary: '分拣员健康与告警' })
  async sorterHealth(@Body('idCard') idCard: string) {
    return this.ok(await this.mapsLiveService.sorterHealth(idCard));
  }
  @Post('/vehicleOnline', { summary: '在线车辆位置' })
  async vehicleOnline() {
    return this.ok(await this.mapsLiveService.vehicleOnline());
  }
  @Post('/vehicleTrack', { summary: '车辆历史轨迹' })
  async vehicleTrack(
    @Body('vehicleNo') vehicleNo: string,
    @Body('startTime') startTime: string,
    @Body('endTime') endTime: string
  ) {
    return this.ok(await this.mapsLiveService.vehicleTrack(vehicleNo, startTime, endTime));
  }
  @Post('/vehicleEvents', { summary: '车辆司机行为消息' })
  async vehicleEvents(@Body('vehicleNo') vehicleNo: string) {
    return this.ok(await this.mapsLiveService.vehicleEvents(vehicleNo));
  }
}
src/modules/maps/service/live.ts
New file
@@ -0,0 +1,293 @@
import { Inject, Provide } from '@midwayjs/core';
import { BaseService } from '@cool-midway/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository, SelectQueryBuilder } from 'typeorm';
import { Context } from '@midwayjs/koa';
import { BasicdataSiteEntity } from '../../basicdata/entity/site';
import { BasicdataSorterEntity } from '../../basicdata/entity/sorter';
import { BasicdataVehicleEntity } from '../../basicdata/entity/vehicle';
import { BasicdataWastetypeEntity } from '../../basicdata/entity/wastetype';
import { BasicdataIotEntity } from '../../basicdata/entity/iot';
import { PushSorterGpsEntity } from '../../push/entity/sortergps';
import { PushSorterHealthEntity } from '../../push/entity/sorterhealth';
import { PushSorterAlarmEntity } from '../../push/entity/sorteralarm';
import { PushVehicleGpsEntity } from '../../push/entity/vehiclegps';
import { PushSiteweightEntity } from '../../push/entity/siteweight';
import { PushDevicealarmEntity } from '../../push/entity/devicealarm';
function n(v: any, d = 0) {
  const x = Number(v);
  return Number.isFinite(x) ? x : d;
}
@Provide()
export class MapsLiveService extends BaseService {
  @Inject()
  ctx: Context;
  @InjectEntityModel(BasicdataSiteEntity)
  siteRepo: Repository<BasicdataSiteEntity>;
  @InjectEntityModel(BasicdataSorterEntity)
  sorterRepo: Repository<BasicdataSorterEntity>;
  @InjectEntityModel(BasicdataVehicleEntity)
  vehicleRepo: Repository<BasicdataVehicleEntity>;
  @InjectEntityModel(PushSorterGpsEntity)
  sorterGps: Repository<PushSorterGpsEntity>;
  @InjectEntityModel(PushSorterHealthEntity)
  sorterHealthRepo: Repository<PushSorterHealthEntity>;
  @InjectEntityModel(PushSorterAlarmEntity)
  sorterAlarm: Repository<PushSorterAlarmEntity>;
  @InjectEntityModel(PushVehicleGpsEntity)
  vehicleGps: Repository<PushVehicleGpsEntity>;
  @InjectEntityModel(PushSiteweightEntity)
  siteWeightRepo: Repository<PushSiteweightEntity>;
  @InjectEntityModel(PushDevicealarmEntity)
  deviceAlarm: Repository<PushDevicealarmEntity>;
  @InjectEntityModel(BasicdataIotEntity)
  iotRepo: Repository<BasicdataIotEntity>;
  private deptIds(): number[] | null {
    const admin = this.ctx.admin;
    if (!admin || admin.username === 'admin') return null;
    return Array.isArray(admin.departmentIds) ? admin.departmentIds.map(Number) : [];
  }
  private applyDept(qb: SelectQueryBuilder<any>, alias: string) {
    const ids = this.deptIds();
    if (ids === null) return qb;
    if (!ids.length) {
      qb.andWhere('1 = 0');
      return qb;
    }
    qb.andWhere(`${alias}.departmentId IN (:...deptIds)`, { deptIds: ids });
    return qb;
  }
  async sites() {
    const qb = this.siteRepo
      .createQueryBuilder('a')
      .select([
        'a.id',
        'a.businessId',
        'a.businessName',
        'a.departmentId',
        'a.siteType',
        'a.address',
        'a.leader',
        'a.longitude',
        'a.latitude',
        'a.photo',
        'a.status',
        'a.remark',
      ])
      .where('a.status = :status', { status: 1 })
      .andWhere('a.longitude IS NOT NULL')
      .andWhere('a.latitude IS NOT NULL');
    this.applyDept(qb, 'a');
    const rows = await qb.getMany();
    return rows.filter(e => n(e.longitude) && n(e.latitude));
  }
  async siteWeight(businessId: string, startTime: string, endTime: string) {
    if (!businessId) return [];
    const qb = this.siteWeightRepo
      .createQueryBuilder('a')
      .leftJoin(BasicdataWastetypeEntity, 'c', 'a.garbageType = c.code')
      .select('a.id', 'id')
      .addSelect('a.businessId', 'businessId')
      .addSelect('a.deviceNo', 'deviceNo')
      .addSelect('a.uploadTime', 'uploadTime')
      .addSelect('a.garbageType', 'garbageType')
      .addSelect('c.name', 'garbageName')
      .addSelect('a.weight', 'weight')
      .where('a.businessId = :businessId', { businessId })
      .andWhere('a.uploadTime BETWEEN :startTime AND :endTime', { startTime, endTime })
      .orderBy('a.uploadTime', 'DESC')
      .limit(200);
    this.applyDept(qb, 'a');
    return qb.getRawMany();
  }
  async sorterOnline() {
    const since = new Date(Date.now() - 15 * 60 * 1000);
    const qb = this.sorterGps
      .createQueryBuilder('a')
      .where('a.uploadTime >= :since', { since })
      .orderBy('a.uploadTime', 'DESC')
      .take(4000);
    this.applyDept(qb, 'a');
    const rows = await qb.getMany();
    const latest = new Map<string, PushSorterGpsEntity>();
    rows.forEach(r => {
      if (r.idCard && !latest.has(r.idCard)) latest.set(r.idCard, r);
    });
    const cards = [...latest.keys()];
    const sorters = cards.length
      ? await this.sorterRepo
          .createQueryBuilder('s')
          .where('s.businessId IN (:...cards)', { cards })
          .getMany()
      : [];
    const sorterMap = new Map(sorters.map(s => [s.businessId, s]));
    return [...latest.values()].map(g => {
      const s = sorterMap.get(g.idCard);
      return {
        idCard: g.idCard,
        name: s?.businessName || g.name,
        phone: s?.phone || '',
        sex: s?.sex,
        workSiteId: s?.workSiteId,
        departmentId: s?.departmentId || g.departmentId,
        departmentName: g.departmentName,
        longitude: n(g.longitude),
        latitude: n(g.latitude),
        speed: n(g.speed),
        uploadTime: g.uploadTime,
        status: s?.status,
      };
    });
  }
  async sorterTrack(idCard: string, startTime: string, endTime: string) {
    if (!idCard) return [];
    const qb = this.sorterGps
      .createQueryBuilder('a')
      .where('a.idCard = :idCard', { idCard })
      .andWhere('a.uploadTime BETWEEN :startTime AND :endTime', { startTime, endTime })
      .andWhere('a.longitude IS NOT NULL')
      .andWhere('a.latitude IS NOT NULL')
      .orderBy('a.uploadTime', 'ASC')
      .take(5000);
    this.applyDept(qb, 'a');
    const rows = await qb.getMany();
    return rows.map(r => ({
      longitude: n(r.longitude),
      latitude: n(r.latitude),
      speed: n(r.speed),
      time: r.uploadTime,
    }));
  }
  async sorterHealth(idCard: string) {
    if (!idCard) return { recent: [], abnormal: [], alarms: [] };
    const recentQb = this.sorterHealthRepo
      .createQueryBuilder('a')
      .where('a.idCard = :idCard', { idCard })
      .orderBy('a.uploadTime', 'DESC')
      .take(20);
    this.applyDept(recentQb, 'a');
    const recent = await recentQb.getMany();
    const abQb = this.sorterHealthRepo
      .createQueryBuilder('a')
      .where('a.idCard = :idCard', { idCard })
      .andWhere("a.dataType = '异常'")
      .orderBy('a.uploadTime', 'DESC')
      .take(20);
    this.applyDept(abQb, 'a');
    const abnormal = await abQb.getMany();
    const alarmQb = this.sorterAlarm
      .createQueryBuilder('a')
      .where('a.idCard = :idCard', { idCard })
      .orderBy('a.uploadTime', 'DESC')
      .take(20);
    this.applyDept(alarmQb, 'a');
    const alarms = await alarmQb.getMany();
    return { recent, abnormal, alarms };
  }
  async vehicleOnline() {
    const since = new Date(Date.now() - 15 * 60 * 1000);
    const qb = this.vehicleGps
      .createQueryBuilder('a')
      .where('a.gpsTime >= :since', { since })
      .andWhere("a.vehicleNo IS NOT NULL AND a.vehicleNo <> ''")
      .orderBy('a.gpsTime', 'DESC')
      .take(4000);
    this.applyDept(qb, 'a');
    const rows = await qb.getMany();
    const latest = new Map<string, PushVehicleGpsEntity>();
    rows.forEach(r => {
      if (r.vehicleNo && !latest.has(r.vehicleNo)) latest.set(r.vehicleNo, r);
    });
    const plates = [...latest.keys()];
    const vehicles = plates.length
      ? await this.vehicleRepo
          .createQueryBuilder('v')
          .where('v.businessId IN (:...plates)', { plates })
          .getMany()
      : [];
    const vehicleMap = new Map(vehicles.map(v => [v.businessId, v]));
    return [...latest.values()].map(g => {
      const v = vehicleMap.get(g.vehicleNo);
      return {
        vehicleNo: g.vehicleNo,
        sn: g.sn,
        vehicleType: v?.vehicleType,
        capacityTon: v?.capacityTon,
        departmentId: v?.departmentId || g.departmentId,
        departmentName: g.departmentName,
        longitude: n(g.longitude),
        latitude: n(g.latitude),
        speed: n(g.speed),
        direction: g.direction,
        gpsTime: g.gpsTime,
        status: v?.status,
        gpsStatus: g.status,
      };
    });
  }
  async vehicleTrack(vehicleNo: string, startTime: string, endTime: string) {
    if (!vehicleNo) return [];
    const qb = this.vehicleGps
      .createQueryBuilder('a')
      .where('a.vehicleNo = :vehicleNo', { vehicleNo })
      .andWhere('a.gpsTime BETWEEN :startTime AND :endTime', { startTime, endTime })
      .andWhere('a.longitude IS NOT NULL')
      .andWhere('a.latitude IS NOT NULL')
      .orderBy('a.gpsTime', 'ASC')
      .take(5000);
    this.applyDept(qb, 'a');
    const rows = await qb.getMany();
    return rows.map(r => ({
      longitude: n(r.longitude),
      latitude: n(r.latitude),
      speed: n(r.speed),
      direction: r.direction,
      time: r.gpsTime,
    }));
  }
  async vehicleEvents(vehicleNo: string) {
    if (!vehicleNo) return [];
    const iots = await this.iotRepo.find({
      where: { businessId: vehicleNo },
      select: ['iotCode', 'iotName', 'iotTypeCode'],
    });
    const codes = iots.map(e => e.iotCode).filter(Boolean);
    if (!codes.length) return [];
    const nameMap = new Map(iots.map(e => [e.iotCode, e.iotName]));
    const qb = this.deviceAlarm
      .createQueryBuilder('a')
      .where('a.devSerial IN (:...codes)', { codes })
      .orderBy('a.createTime', 'DESC')
      .take(30);
    const rows = await qb.getMany();
    return rows.map(r => ({
      ...r,
      deviceName: nameMap.get(r.devSerial) || r.devSerial,
    }));
  }
}