阅读 949

如何设计一个数据采集器?

场景

  • 在业务上现在有一个场景,当发生业务行为变化时,需要对各个模块的行为进行数据收集,数据用途可以用作回顾,也可以是例如监控这样的场景。

核心问题

  • 说白了这个需求就是需要对各个模块状态变更进行记录,然后再格式化上传到服务端。
  • 解题思路有两种一种是状态监听,第二主动收集。

状态监听

状态监听优势

  • 快速实现利用状态管理和wacth的机制很快就知道不同模块的状态变更,然后就可以获取数据,再格式化数据,发送给服务端

状态监听劣势

  • wacth的重复监听,只要使用了wacth,不管是不是你所需要的数据,只要状态变更就会触发改变,监听行为
  • 重复依赖,比如说全局有个开始结束的状态,在使用wacth的时候就需要在不同的wacth中都去判断这个状态,或者有全局的时间模块等等
  • 重复书写,在不同的监听中需要实践相同的数据格式化方法
  • 数据分布混乱,虽然控制了全局使用同一管道上传,但是对于同一个管道内的数据想做合并去重,或者其他自定义的操作,在不同类型数据,同一管道的这个场景下面支持很弱
  • 场景区分困难,正常流程触发的监听是没有问题,如果是异常场景触发恢复的监听就会导致判断的复杂性
  • 描述的还是比较抽象看下代码示例
function useA(){
 wacth(new,old){
  if(start){
    if(new.type =='need')
     const a = {
      a:new.a
     }
     const aa = {
      aa:new.aa
     }
     upload(a)
     upload(aa)
  }
 }
}
// 多处数据散落
function useB(){
 // 重复监听
 wacth(new,old){
 // 全局判断
  if(start){
    // 不同状态判断
    if(new.type =='need')
     const b = {
      b:new.b
     }
     //重复数据格式
     const aa = {
      b:new.aa
     }
     upload(b)
     upload(aa)
  }
 }
}
复制代码

重构实现思路

  • 依赖收集(监听者模式)
  • 状态统一
  • 数据自治(策略模式)

依赖收集

  • 核心思想:希望使用同一个采集器解决整个业务流程,数据变更在各个变更方,通过采集器提供的标准的格式化方法去处理数据,再把数据传递到采集器,采集器收到数据后根据不同的数据格式插入到不同的缓存通道,缓存通道缓存成功,通知业务处理的监听者,根据不同的数据类型进行不同的处理方式,最后发送到服务端。
  • 具体代码如下
/*
 * @Description: 采集公共类
 * @version: 1.0.0
 * @Author: 吴文周
 * @Date: 2021-04-20 19:44:35
 * @LastEditors: 吴文周
 * @LastEditTime: 2021-04-22 15:20:50
 */
/**
 * @name: Dep
 * @msg: 依赖收集对象
 */
class Dep {
  private subs: any = []
  // 添加观察者
  public addSub(sub: any) {
    if (sub && sub.update) {
      this.subs.push(sub)
    }
  }
  // 发送通知
  public notify(content: any) {
    this.subs.forEach((sub: any) => {
      sub.update(content)
    })
  }
}
/**
 * @name: Watcher
 * @msg: 观察者对象
 */
class Watcher {
  private cb!: (arg: any) => void
  constructor(cb: (arg: any) => void) {
    // 回调函数负责更新
    this.cb = cb
  }
  // 当数据发生变化的时候更新
  update(content: any) {
    this.cb(content)
  }
}
/**
 * @name: Channel
 * @msg: 缓存消息管道
 */
class Channel {
  // 管道存储数组
  private queue: any = []
  // 管道大小
  private limitSize = 1
  // 管道名称
  public name: string
  constructor(name: string, limitSize = 1) {
    this.name = name
    // 最小尺寸是1
    limitSize = limitSize >= 1 ? limitSize : 1
    this.limitSize = limitSize
  }
  /**
   * @name: push
   * @msg: 添加的数据
   */
  push(item: any) {
    // 如果超出限制尺寸移除第一个
    if (this.limitSize == this.queue.length) {
      this.queue.shift()
    }
    this.queue.push(item)
  }
  /**
   * @name: getLast
   * @msg: 获取最后添加的数据
   */
  getLast() {
    if (this.queue.length > 0) {
      return this.queue[this.queue.length - 1]
    } else {
      throw new Error('no item return')
    }
  }
  /**
   * @name: getLastIndex
   * @msg: 获取最后倒数的数据
   */
  getLastIndex(index: number) {
    try {
      return this.queue[this.queue.length - index - 1]
    } catch (error) {
      throw new Error('no item return')
    }
  }
  /**
   * @name: isEmpty
   * @msg: 管道是否为空
   */
  isEmpty() {
    return this.queue.length == 0
  }
}
export class Collection {
  // 依赖收集对象
  private dep = new Dep()
  // 各个数据频道分类
  private dataQueue = ['A', 'B', 'C']
  // 频道集合
  private channelMap = new Map()
  // 上传队列
  private MQ!: LiveCollectionMQ
  // 策略模式:数据类型不同对应不同的处理机制
  private strategies = {
    A: () => {
      // 可以在不同的管道中获取相对应的数据进行不同逻辑的处理
    },
    B: () => {

    },
    C: () => {
    },
  } as Record<NotifyType, any>
  constructor() {
    this.init()
  }
  private init() {
    // 初始化watcher
    this.intWatcher()
    // 初始化频道
    this.initChannel()
    // 初始化数据
    this.initData()
    // 初始化队列
    this.initMQ()
  }
  /**
   * @name:intWatcher
   * @msg:初始化监听器
   */
  private intWatcher() {
    this.dep.addSub(
      new Watcher((type: NotifyType) => {
        const handlerBack = this.getHandler(type)
        handlerBack()
      }),
    )
  }
  /**
   * @name: initChannel
   * @msg: 初始化频道
   */
  private initChannel() {
    this.dataQueue.forEach(item => {
      this.channelMap.set(item, new Channel(item, 3))
    })
  }
  /**
   * @name: initData
   * @msg: 初始化频道数据
   * @param {*}
   */
  private initData() {
    
  }
  /**
   * @name: initMQ
   * @msg: 初始化上传队列
   */
  private initMQ() {

  }
  /**
   * @name: getMQ
   * @msg:获取消息队列
   */
  public getMQ() {
    return this.MQ
  }
  /**
   * @name:getChannel
   * @msg:根据频道名称获取频道实例
   * @param {name}频道名称
   */
  private getChannel(name: NotifyType) {
    if (this.channelMap.get(name)) {
      return this.channelMap.get(name)
    } else {
      throw new Error('no channel')
    }
  }
  /**
   * @name:notify
   * @msg:依赖通知方法
   * @param {NotifyType} type
   * @param {any} mes
   */
  public notify(type: NotifyType, mes: any) {
    // 设置管道缓存
    this.setChannel(type, mes)
    // 全局统一判断状态判断是否要分发数据
    if (state.value.type) {
      this.dep.notify(type)
    }
  }
  /**
   * @name: setChannel
   * @msg: 设置频道缓存
   * @param {NotifyType} name
   * @param {any} mes
   */
  private setChannel(name: NotifyType, mes: any) {
    const channel = this.getChannel(name)
    channel.push(mes)
  }
  /**
   * @name:getHandler
   * @msg: 获取
   * @param {NotifyType} name
   */
  private getHandler(name: NotifyType) {
    return this.strategies[name]
  }
  /**
   * @name: getChannelLast
   * @msg: 获取指定管道中的最新的数据
   * @param {NotifyType} name
   * @return {*}
   */
  public getChannelLast(name: NotifyType) {
    try {
      const channel = this.getChannel(name)
      return channel.getLast()
    } catch (error) {
      throw new Error(error)
    }
  }
  /**
   * @name: getChannelLast
   * @msg: 获取指定管道中的倒序数据
   * @param {NotifyType} name
   * @param {number} index
   */
  public getChannelItemByLastIndex(name: NotifyType, index: number) {
    try {
      const channel = this.getChannel(name)
      return channel.getLastIndex(index)
    } catch (error) {
      throw new Error(error)
    }
  }
  /**
   * @name: generateA
   * @msg: 生成A数据方法
   */
  public generateA() {
    
  }
  /**
   * @name: generateB
   * @msg: 生成B数据方法
   */
  public generateB() {
    
  }
  /**
   * @name: generateC
   * @msg: 生成C数据方法
   */
  public generateC() {
    
  }
}

export const CollectionHelper = new Collection()

复制代码

总结

  • 我觉得去了解一个框架的一个好的思路就是在运用它的核心原理去解决一个原理,正如之前使用webpack的插件机制一样,这次使用的是vue的依赖收集
  • 状态自治,职责统一是个代码封装的好习惯
文章分类
前端
文章标签