0%

苹果App主体迁移

苹果App主体迁移

苹果App主体迁移-苹果登录

迁移流程图

![](https://hwl-1255548986.cos.ap-guangzhou.myqcloud.com/hwl.me苹果主体迁移 流程图 .png)

迁移前操作

获取苹果转移标识符 transfer_sub 苹果官方文档

客户端提供参数:

  1. client_id
  2. team_id
  3. key_id
  4. 苹果p8文件
  5. recipient_team_id

后端操作步骤

  1. 获取client_secret,有效期可配置
  2. 获取accessToken,缓存一小时
  3. 获取转移标识符transfer_sub,并入库

获取client_secret

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 使用ruby fileName.rb执行以下代码,获取client_secret
# 需安装jwt
# gem install jwt -v 1.5.4
require "jwt"
key_file = "AuthKey_xxxxx.p8" # 客户端提供的p8文件路径
team_id = "team_id"
client_id = "bundle_id"
key_id = "key_id"
validity_period = 180
private_key = OpenSSL::PKey::EC.new IO.read key_file
token = JWT.encode(
{
iss: team_id,
iat: Time.now.to_i,
exp: Time.now.to_i + 86400 * validity_period,
aud: "https://appleid.apple.com",
sub: client_id
},
private_key,
"ES256",
header_fields=
{
kid: key_id
}
)
puts token

获取AccessToken

1
2
3
curl --location --request POST 'http://appleid.apple.com/auth/token?grant_type=client_credentials&scope=user.migration&client_id={bundleId}&client_secret={刚刚上面生成的client_secret}' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-raw ''
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 新加坡请求苹果服务器耗时大概是1000ms
const getAppleAccessToken = async () => {
const key = redisKey.string.appleAccessToken;
let accessToken = await redis.get(key);
if (!accessToken) {
const startTime = new Date().getTime();
const bundleId = 'bundle_id';
const clientSecret = 'clientSecret';
const url = `http://appleid.apple.com/auth/token?grant_type=client_credentials&scope=user.migration&client_id=${bundleId}&client_secret=${clientSecret}`;
const options = {
method: 'POST',
uri: url,
followAllRedirects: true,
followOriginalHttpMethod: true,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
}
};
const data = await request(options).catch((err) => {
console.log(err);
});
const endTime = new Date().getTime();
const result = JSON.parse(data);
if (result.access_token) {
// 苹果accessToken有效期1小时,不会覆盖,不会因为生成新的,导致旧的token失效
accessToken = result.access_token;
console.log(`新生成accessToken ${JSON.stringify(result)}`, '耗时', endTime - startTime);
await redis.set(key, accessToken, 'EX', 3600);
}
}
return accessToken;
};

获取转移标识符transfer_sub

1
2
3
curl --location --request POST 'http://appleid.apple.com/auth/usermigrationinfo?sub={用户苹果登录时传的appleUserId}&target={target_id}&client_id={bundleId}&client_secret={{刚刚上面的上面生成的client_secret}}' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Bearer {刚刚上面获取的accesstoken}'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 新加坡请求苹果服务器耗时大概是1000ms
const getTransferSub = async (appleUserId, token) => {
const startTime = new Date().getTime();
const bundleId = 'bundle_id'; // App的bundle_id
const targetId = 'targetId'; // 接收App的团队id,recipient_team_id
const clientSecret = 'clientSecret';
const url = `http://appleid.apple.com/auth/usermigrationinfo?sub=${appleUserId}&target=${targetId}&client_id=${bundleId}&client_secret=${clientSecret}`;
const options = {
method: 'POST',
uri: url,
followAllRedirects: true,
followOriginalHttpMethod: true,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': `Bearer ${token}`
}
};
const data = await request(options).catch((err) => {
console.log('获取TransferSub失败', JSON.stringify(err), err.message, redisKey.string.appleAccessToken); // 有部分用户可能取消了苹果登录的授权,所以会失败
throw new Error('getTransferSubError');
});
const endTime = new Date().getTime();
console.log(`获取TransferSub ${data}`, '耗时', endTime - startTime);
const result = JSON.parse(data);
if (result.error) {
console.log('token过期', result.error, redisKey.string.appleAccessToken);
// token过期,删除原来的缓存
await redis.del(redisKey.string.appleAccessToken);
throw new Error('token_expires');
}
return result.transfer_sub;
};

通过队列消费任务

我这边最多使用了30个容器同时消费,并没有被苹果因为QPS的原因拒绝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
channel.addSetup(async function(channel) {
const prefetch = parseInt(process.env.prefetch) || 1;
await channel.assertQueue('apple.login.userId.record', { durable: true });
await channel.prefetch(prefetch);
channel.setConfig({
DEAD_LETTER_TTLs: [5, 10, 30], // 重试配置
});
await channel.consumerQueue('apple.login.userId.record', async function(data) {
console.log('苹果登录用户转移标识符记录:', JSON.stringify(data.username));
await recordUserAppleTransferSub(data);
});
});
const recordUserAppleTransferSub = async (data) => {
const appleUserId = data.username;
if (appleUserId) {
const accessToken = await getAppleAccessToken();
if (accessToken) {
let transferSub = await getTransferSubByAppleUserId(appleUserId);
if (!transferSub) {
transferSub = await getTransferSub(appleUserId, accessToken);
await updateTransferSubUserData(appleUserId, transferSub);
} else {
console.log('该用户已经获取了transferSub', JSON.stringify(transferSub), appleUserId);
}
}
}
};

迁移后

获取新的appleUserId 苹果官方文档

客户端提供参数:

  1. client_id
  2. team_id
  3. key_id
  4. 苹果p8文件

后端操作步骤

  1. 获取client_secret,有效期可配置
  2. 获取accessToken,缓存一小时
  3. 通过transfer_sub和token,获取新的AppleUserId,并入库

获取新的client_secret

客户端提供新的参数,使用之前的脚本获取client_secret

获取AccessToken

使用新的client_secret获取AccessToken

获取新的AppleUserId(遍历之前的苹果登录转移标识符表)

1
2
3
curl --location --request POST 'http://appleid.apple.com/auth/usermigrationinfo?transfer_sub={transfer_sub}&client_id={bundleId}&client_secret={{刚刚上面的上面生成的client_secret}}' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Bearer {刚刚上面获取的accesstoken}'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const getNewAppleUserId = async (transferSub, token) => {
const startTime = new Date().getTime();
const bundleId = 'bundle_id';
const url = `http://appleid.apple.com/auth/usermigrationinfo?transfer_sub=${transferSub}&client_id=${bundleId}&client_secret=${clientSecret}`;
const options = {
method: 'POST',
uri: url,
followAllRedirects: true,
followOriginalHttpMethod: true,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': `Bearer ${token}`
}
};
const data = await request(options).catch((err) => {
console.log('获取NewAppleUserId失败', JSON.stringify(err), err.message, redisKey.string.appleAccessToken);
throw new Error('getNewAppleUserId');
});
const endTime = new Date().getTime();
console.log(`获取NewAppleUserId ${data}`, '耗时', `${endTime - startTime}ms`);
const result = JSON.parse(data);
if (result.error) {
console.log('token过期', result.error, redisKey.string.appleAccessToken);
await redis.del(redisKey.string.appleAccessToken);
throw new Error('token_expires');
}
return result.sub;
};

将新AppleUserId入库,建立新旧AppleUserId的映射关系

在用户表中新增newAppleUserId字段,建立部分索引
当用户调用苹果登录时,先判断用户客户端是否为新版客户端,如果是则查询

1
ctx.model.User.findOne({ $or: [{ username: appleUserId }, { newAppleUserId: appleUserId }] })

苹果App主题迁移-游客登录

客户端上报deviceId和openId

1
app.router.post('/apple_device_guest', app.controller.device.openIdAndDeviceId); // 苹果主体迁移临时记录

客户端通过deviceId获取openId

1
app.router.get('/apple_device_guest', app.controller.device.getOpenIdByDeviceId); // 获取openId

##新版游客登录

  1. 判断客户端版本是否是在迁移后的新版
  2. 是的话先通过IDFA(deviceId)获取openId
  3. 通过openId找到原来的账号进行登录
  4. 如果没有IDFA则注册新账号进行登录