阿里推送-历史标签惹的祸

241 阅读7分钟

深秋的午后,写字楼里弥漫着一股慵懒的气息。测玲习惯性地伸了个懒腰,揉了揉略显疲惫的双眼。作为一名经验丰富的上班族,她深谙工作节奏的重要性——每工作一小时,她都会给自己一个短暂的"数字解压时间",通常是刷刷手机,看看姐妹们在小红书上的分享。这个看似简单的习惯,却让她在繁忙的工作中始终保持着不错的效率。

2024年11月1日,这个平淡无奇的工作日,原本应该像往常一样平静地流逝。然而就在测玲准备开始她例行的"手机时间"时,屏幕上突然跳出了一连串诡异的推送通知。那些字符歪歪扭扭地挤在一起,像是某种无法辨认的密码,又像是系统出了故障。更让她在意的是,这些奇怪的推送竟然来自公司自己的App。

作为一个做事严谨的人,测玲的第一反应是这些乱码应该是开发测试时的遗留问题。但职业素养告诉她,在这种情况下不能轻易下定论。她转头看向隔壁工位的同事小林,"你收到奇怪的推送了吗?"小林闻言也掏出手机查看,果然,同样的乱码也出现在她的手机上。

她看了一下她安装 App 是属于公司的正式版本,她喃喃自语到:“如果这是开发测试发的推送,那么我的由于是正式版本,那么就不应该收到来自测试的推送。转念一想要不我就不管了,反正不是我的问题,但又想如果这真是问题的话,这些消息所有的客户都能收到”。测玲轻轻的跺跺脚,又喃喃说道:“算了我还是去群里说说”。于是测玲打开群聊,把收到的消息截图发到群里,并询问是不是公司正在测试啥的文案,然后她就继续她的工作。

开发人员小吴看到群里面有人发消息,一看是手机中出现,于是小吴把消息告诉测试人员雍墨嫣,雍墨焉拿起手机针对群里的情况进行复现。另外一个开发在群里表述是因为他正在开发推送相关的功能,刚刚正在测试推送。

小吴公司推送采用的是阿里推送,同时 App 开发采用的是 react-native 跨端开发框架;利用阿里推送的标签来进行推送,也就是说如果要给 A 用户,那么就需要给 A 用户添加标签 A ,同样还会有一些标签标识特定人群,或者是否全量。见下图的大致流程图:

react-native 使用的库是 react-native-aliyun-emas 。为了统一管理标签的绑定与解绑,小吴当初通过 useEffect 进行的:

const initTag = useCallback(() => {
  const tempAccountTag = accountTag;
  const tempEventId = eventId;
  const tempNoAccountTag = noAccountTag;
  const tempAllPhoneTag = allPhoneTag;
  destroyRef.current.finally(async () => {
    await AliyunPush.bindTag(
      1,
      token ? [tempAllPhoneTag, tempAccountTag, tempEventId] : [tempAllPhoneTag, tempNoAccountTag],
      '',
    );
  });
  return async () => {
    destroyRef.current = AliyunPush.unbindTag(
      1,
      [tempAccountTag, tempEventId, tempNoAccountTag, tempAllPhoneTag],
      '',
    )
  };
}, [accountTag, allPhoneTag, eventId, noAccountTag, token]);

useEffect(() => {
  return initTag();
}, [initTag]);

其中 destroyRef.current 表示要解绑的标签。其中为啥要使用这个变量呢,是因为在绑定前需要先将解绑的逻辑执行完,不然可能出现绑定先完成,解绑后完成,这样就容易出现一些标签需要绑定的,结果却没有。反复推演都觉得标签这里没有问题。一下小吴陷入郁闷中,不知道如何下手,又重新梳理“嫌疑人”。

  1. 目前代码本身逻辑:已验证没有问题;
  2. 标签数据:由于这些数据保存到阿里推送,暂不清楚存储的数据是否有问题;
  3. 账户:既然有人反馈有问题,那么就尝试完全模拟“凶发现场”来看看是否有问题,测试雍墨嫣测试并没有发现问题;
  4. 手机:会不会跟用户的手机有关系,比如手机的型号。

把目前可能存在的“嫌疑人”都进行一一排查:

目前代码本身逻辑:其中第一个嫌疑人已经排除了。

标签数据:这点不方便排查,我们并不知道其存储原理,只不过可以通过查找官方文档,根据其定义来大致推断其原理:

标签(Tag) :标签名称设置要小于等于128个字符(中文算1个),每个App最多可创建10000个标签,一

个设备可以绑定不限数量的标签,一个标签也可以绑定不限数量的设备。

标签是跟设备关联的,也就是假如你的设备 id 为: abc123 ,那么使用这个设备绑定的标签隶属于这个 id ;如果你的 id 改变了,那么就不会生效,也就是说如果你换手机了之前的标签就会失效;打个比方如果你们的 App 支持多账号登录, A 设备假如有 a ,b ,c 三个标签; B 设备也是登录的同一个账号,但是在 B 设备绑定的 c , d , f 这三个标签。服务端根据标签推送,当推送 c 的时候,这两个手机都能收到;而如果按 a 来推送,那么 B 设备就收不到推送。

账户:在开发环境登录测玲账号,发现测玲账号下有测试环境下的标签。

手机:最没有可能,先不做深入调查。

结合以上“口供和调查举证”得出:账户提供的证据表明,看到非当地人经过,再结合标签数据的资料可以推断这似乎是外地人作案,或者不是本小区的人,但是熟悉这个小区的人进行的。这时小吴突然想到上面的代码是从之前改过后得到的,而之前的代码为:

useEffect(() => {
  const allPush = allPhoneTag;
  const accountPush = accountTag;
  if (token) {
    // 绑定
    AliyunPush.bindTag(1, [allPush, accountPush, eventId], '').then(
      result => {
        console.log('登录后:bindTag success' + JSON.stringify(result));
      },
    );
    // 解绑
    AliyunPush.unbindTag(1, [noAccountTag], '');
    return () => {
      AliyunPush.unbindTag(1, [allPush, accountPush, noAccountTag], ''); // 解绑
    };
  } else {
    // 绑定
    AliyunPush.bindTag(1, [allPush, noAccountTag], '').then(result => {
      console.log('未登录' + JSON.stringify(result));
    });
    AliyunPush.unbindTag(1, [accountPush, eventId], ''); // 解绑
    Logger.message(accountTag + '---' + email);
  }
}, [
  accountTag,
  allPhoneTag,
  child_id,
  currentLang,
  email,
  noAccountTag,
  serviceLocation,
  eventId,
  token,
  userId,
  accountId,
]);

这段代码会出现之前绑定的标签不会被解绑掉,比如绑定的测试环境标签,变成正式环境后,并没有先卸载之前的测试环境标签。那么只要登录过测试环境,那么测试环境标签就一直存在;虽然我修改过每次都能正常解绑和绑定,但是对于之前出现问题的标签并没有进行处理。

现在已经破案了,是由于之前的问题导致的;这个时候小吴进一步思考怎样才能把之前的标签更正。阿里推送还有一个 API 可以查询当前设备下有多少标签;由于使用 useEffect 来进行管理,每一次其中一个变动都会导致里面的逻辑重新执行,既然如此,那么就每次绑定前都把之前的解绑就可以了。使用 useEffect 来进行执行异步相关操作,还有可能依赖反复变动而出现很多任务堆积,这个时候执行的顺序就难以保证,具体的问题参考这篇文章:用 useEffect 优雅管理 React 中的异步操作

当小吴修复了问题并在群里解释了原因,大家都为此感到高兴。

这个案件告诉我们,跟数据有关的 bug 一定要考虑到历史,也就是历史数据导致的问题,准确的说过去逻辑缺陷或业务缺陷而导致的数据问题;同样也告诉我们有时候我们之所以前进得很艰难,也许也是因为历史/经验导致的,这个时候我们要学会丢掉包袱/经验,要懂得兼容过去。