返回

iOS 钱包设备标识符重复注册问题解析与解决

IOS

iOS 钱包中设备标识符重复注册问题

在 iOS 钱包 (Wallet) 中,单个卡券 (Pass) 出现多个 deviceLibraryIdentifier 注册,并非个例。这类情况会带来一系列问题,例如同一张卡券在同一设备上收到重复更新推送。这可能会引起用户体验下降,同时也增加了服务器端的处理负担。

问题分析

通常,当用户首次向设备添加一张卡券时,设备会生成一个 deviceLibraryIdentifier,并发送注册请求到你的服务器。随后,你就可以用此标识符向用户的设备推送更新。问题在于,在某些情况下,iOS 钱包似乎会在极短的时间内为同一卡券生成多个 deviceLibraryIdentifier。这种行为导致同一设备上的同一张卡券存在多个设备注册信息,而实际上你只需要一个。

从提供的日志片段看,服务器几乎同时收到了针对同一卡券的不同 deviceLibraryIdentifier 注册请求。可以明显看到,/v1/devices/v1/devices/{deviceLibraryIdentifier}/registrations 路由连续出现。这暗示着设备端可能在初始化卡券或者发生其他情况时触发了重复的注册过程。

解决方案

处理此问题有多种方法,此处提供一些较有效的解决方案。

1. 延迟注册与幂等性处理

最简单方法是在服务器端实施延迟注册和幂等性逻辑。收到新的注册请求后,首先不要立即创建新的设备记录,而是检查是否存在时间戳在最近一段时间内(例如30秒到1分钟内)针对此卡券和此设备的注册记录。若有,则视为重复注册,直接更新现有记录即可。

import time

def register_device(pass_type_id, serial_number, device_library_identifier):
  now = time.time()
  # 查询是否存在针对此 pass 和设备,且在最近 60 秒内发生的注册
  existing_registration = query_db(pass_type_id, serial_number, device_library_identifier, time_limit = 60)

  if existing_registration:
        # 更新时间戳, 将数据合并或更新其他信息
        update_registration(existing_registration['id'], now)
        return "update_success",200
  else:
        #创建新的注册记录
        create_registration(pass_type_id,serial_number,device_library_identifier,now)
        return "registration_success", 201


def query_db(pass_type_id, serial_number, device_library_identifier, time_limit = 60):
    #连接数据库
    #select 数据进行判断
    #这里要通过时间筛选是否是在 time_limit 时间范围内的,在范围内即返回对应注册数据
    #不在范围内或者不存在,就返回 null 或 False
    pass

def update_registration(register_id, now_time):
     #update database by id

def create_registration(pass_type_id,serial_number,device_library_identifier, now_time):
    #Insert database

步骤

  1. 收到 POST /v1/devices/...注册请求。
  2. 使用 pass_type_idserial_number,以及device_library_identifier 查询是否有近期注册信息
  3. 如有,更新其注册时间。
  4. 若无,创建新的注册记录。
  5. 针对delete请求,需要进行时间限制判断,当注册请求超过一定时间后,才能允许被删除。

原理:

通过时间窗口和幂等操作确保,重复注册尝试仅会导致现有记录的更新。 此策略利用重复注册倾向于在短时间内发生的特性。

2. 设备标识符规范化处理

部分情况下,iOS 可能会生成在某些方面存在细微差别的 deviceLibraryIdentifier。尽管它们指向同一设备,但字符串本身却有所不同。在将 deviceLibraryIdentifier 存储到数据库前,可以对其进行标准化。例如,可以将字母全部转为大写或小写,或去除任何无关的特殊字符。这样做可以有效地减少因细微差别而导致的重复记录。

import re

def normalize_device_id(device_library_identifier):
  """将设备标识符转换为统一的格式。"""
  device_id = device_library_identifier.lower()
  device_id = re.sub(r'[^a-z0-9]', '', device_id) # remove unexpect char
  return device_id

def register_device(pass_type_id, serial_number, device_library_identifier):
  normalized_device_id = normalize_device_id(device_library_identifier)
  existing_registration = query_db(pass_type_id, serial_number, normalized_device_id)
  if existing_registration:
    update_registration(existing_registration['id'],normalized_device_id)
    return "update_success",200
  else:
    create_registration(pass_type_id,serial_number,normalized_device_id)
    return "registration_success", 201

步骤:

  1. 接收到注册请求中的deviceLibraryIdentifier
  2. 使用normalize_device_id标准化此ID。
  3. 使用标准化的ID进行数据库查询和注册操作。
    原理: 通过标准化的设备标识符可以减少由于字符串微小差异造成的重复注册。

3. 基于客户端标记的处理 (风险较高)

还可以考虑让客户端在初次注册后缓存deviceLibraryIdentifier, 并确保其在后续操作中使用。但是,这可能引入更多复杂性。客户端如果遇到无法从缓存读取信息或者出现其他客户端错误情况,可能会导致更多问题。

风险提示: 这个方案存在风险。不当实现可能导致客户端与服务器状态不一致,反而会引入更多问题,应谨慎使用。

其他建议

  • 务必在卡券过期后清除相关注册数据,确保不会无谓的推送通知。
  • 务必仔细测试所有变更,模拟设备丢失或注销情景以防止信息泄露。
  • 定期检查数据库中的注册记录,排除无效或异常的条目,维持系统的整洁和高效。

采用以上策略能够显著地减少 deviceLibraryIdentifier 的重复注册问题,维护 iOS 钱包推送服务的稳定性和效率。处理此类问题没有统一的解决方案,上述方法并非互斥的,可按需求将它们结合使用。