确保 firestore 集合 docChanges 保持活动状态

Make sure firestore collection docChanges keeps alive

最终的解决方案在这个post的底部。

我有一个监听相当大集合的 nodeJS 服务器应用程序:

//here was old code

这工作得很好:这些文档很多,服务器可以从缓存而不是数据库中为它们提供服务,这节省了我大量的文档读取(而且速度快得多)。

我想确定一下,这个集合会一直存在永远,这意味着如果没有发生变化就重新连接。

有什么方法可以创造这种确定性吗?此服务器可能已在线多年。

最终解决方案:

export const lastRolesChange = functions.firestore
  .document(`${COLLECTIONS.ROLES}/{id}`)
  .onWrite(async (_change, context) => {
    return firebase()
      .admin.firestore()
      .collection('syncstatus')
      .doc(COLLECTIONS.ROLES)
      .set({
        lastModified: context.timestamp,
        docId: context.params.id
      });
  });
import { firebase } from '../google/auth';
import { COLLECTIONS } from '../../../configs/collections.enum';

class DataObjectTemplate {
  constructor() {
    for (const key in COLLECTIONS) {
      if (key) {
        this[COLLECTIONS[key]] = [] as { id: string; data: any }[];
      }
    }
  }
}

const dataObject = new DataObjectTemplate();

const timestamps: {
  [key in COLLECTIONS]?: Date;
} = {};

let unsubscribe: Function;

export const getCachedData = async (type: COLLECTIONS) => {
  return firebase()
    .admin.firestore()
    .collection(COLLECTIONS.SYNCSTATUS)
    .doc(type)
    .get()
    .then(async snap => {
      const lastUpdate = snap.data();

      /* we compare the last update of the roles collection with the last update we
       * got from the listener. If the listener would have failed to sync, we
       * will find out here and reset the listener.
       */

      // first check if we already have a timestamp, otherwise, we set it in the past.
      let timestamp = timestamps[type];
      if (!timestamp) {
        timestamp = new Date(2020, 0, 1);
      }

      // if we don't have a last update for some reason, there is something wrong
      if (!lastUpdate) {
        throw new Error('Missing sync data for ' + type);
      }

      const lastModified = new Date(lastUpdate.lastModified);

      if (lastModified.getTime() > timestamp.getTime()) {
        console.warn('Out of sync: refresh!');
        console.warn('Resetting listener');
        if (unsubscribe) {
          unsubscribe();
        }
        await startCache(type);
        return dataObject[type] as { id: string; data: any }[];
      }
      return dataObject[type] as { id: string; data: any }[];
    });
};

export const startCache = async (type: COLLECTIONS) => {
  // tslint:disable-next-line:no-console
  console.warn('Building ' + type + ' cache.');
  const timeStamps: number[] = [];
  // start with clean array

  dataObject[type] = [];

  return new Promise(resolve => {
    unsubscribe = firebase()
      .admin.firestore()
      .collection(type)
      .onSnapshot(querySnapshot => {
        querySnapshot.docChanges().map(change => {
          timeStamps.push(change.doc.updateTime.toMillis());

          if (change.oldIndex !== -1) {
            dataObject[type].splice(change.oldIndex, 1);
          }
          if (change.newIndex !== -1) {
            dataObject[type].splice(change.newIndex, 0, {
              id: change.doc.id,
              data: change.doc.data()
            });
          }
        });
        // tslint:disable-next-line:no-console
        console.log(dataObject[type].length + ' ' + type + ' in cache.');
        timestamps[type] = new Date(Math.max(...timeStamps));
        resolve(true);
      });
  });
};

如果您想确保所有更改都已完成,您必须:

  1. 在每个文档中保留一个 lastModified 类型字段,
  2. 使用查询获取自您上次查看后我们修改的文档,
  3. 存储您上次在服务器上查询的时间。

与此无关,您可能还对最近推出的 serve bundled Firestore content 功能感兴趣,因为这是减少您必须对 Firestore 服务器执行的收费读取次数的另一种方法。