微信小游戏登录接入
微信小游戏登录接入
UOS Passport 为微信小游戏的开发者提供外部登录的方式来接入 Passport Feature 的相关服务。
我们提供Func Stateless云函数根据客户端玩家的登录凭证code获取微信小游戏提供的openID,作为UOS Passport外部账号系统的唯一ID,使用external login登录获取玩家token,供客户端使用。
流程如下:
安装配置 UOS Launcher
参考 Launcher 教程,安装 Launcher 后,关联 UOS APP, 开启 Passport Feature 和 Func Stateless 服务并安装相应SDK。
注意:请务必确认在进行后续教程之前,已经成功完成了 Launcher 教程的安装步骤,否则可能导致后续接入无法顺利进行。
Func stateless云函数接入微信小游戏登录
1.1. 配置Func Stateless C#
参考「Func Stateless C# 开发指南」开启Func - Stateless服务,了解基本使用方法。
1.2. 在客户端代码中创建云函数
在CloudService目录下的文件中编写需要使用的云函数,传入客户端使用wx.login获取用户登录凭证code,获取玩家token。
示例代码如下,注意需要自行填入微信小游戏的appID和appSecret:
using System;
using System.Threading.Tasks;
using Unity.UOS.Func.Stateless.Core.Attributes;
using UnityEngine;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using Newtonsoft.Json;
namespace CloudService
{
// 注意事项
//
// 1. 云函数类所在脚本文件的名称必须与类名相同。
//
// 2. 所有的类必须放置于命名空间内,且所用到的代码文件必须放到同一目录中。
//
// 3. 使用 [CloudService] 标记有远程调用函数的类,使用 [CloudFunc] 标记需要远程调用的函数。
//
// 4. 请在云函数类构造函数中初始化云函数,不要创建并调用其他带有参数的构造函数。
//
// 5. 切换到远程模式后云函数类只会保留带有 [CloudFunc] 的方法,其他字段将会被隐藏。
//
// 6. 使用 [CloudFunc] 标记的函数必须符合 public async Task<返回数据类型> 函数名称(输出参数) { 函数体 } 这样的格式。
//
// 7. 编写代码中只能使用 UnityEngine 命名空间下的 Debug.Log,Debug.LogWarning,Debug.LogError 函数,不能使用其他函数。
//
public class WeixinLoginResult
{
// 可以自行根据微信的返回结果增加字段
public string openid;
}
public class ExternalLoginRequest
{
public string externalUserID;
}
public class ExternalLoginResponse
{
public string personaAccessToken;
public string personaRefreshToken;
public int expiresAt;
public PersonaInfo persona;
}
public class PersonaInfo
{
public string userID;
public string personaID;
}
[CloudService]
public class WechatAPI
{
public WechatAPI()
{
}
[CloudFunc]
public async Task<ExternalLoginResponse?> WechatLogin(string code)
{
var baseUrl = "https://api.weixin.qq.com";
var client = new HttpClient();
// 填入微信小游戏appID
var appID = "";
// 填入微信小游戏appSecret
var appSecret = "";
try
{
// 根据传入的用户登录凭证code从微信端获取玩家openID
var url = baseUrl +
$"/sns/jscode2session?appid={appID}&secret={appSecret}&js_code={code}&grant_type=authorization_code";
var response = await client.GetAsync(url);
var str = await response.Content.ReadAsStringAsync();
Debug.Log("GetOpenid result");
Debug.Log(str);
var result = JsonConvert.DeserializeObject<WeixinLoginResult>(str);
// UOS Passport external login,获取玩家相关token
var externalLoginUrl = "https://p.unity.cn/v1/login/external";
ExternalLoginRequest externalLoginRequest = new ExternalLoginRequest();
externalLoginRequest.externalUserID = result.openid;
var requestStr = JsonConvert.SerializeObject(externalLoginRequest);
HttpContent content = new StringContent(requestStr);
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var appId = Environment.GetEnvironmentVariable("UOS_APP_ID");
var appServiceSecret = Environment.GetEnvironmentVariable("UOS_APP_SERVICE_SECRET");
var credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{appId}:{appServiceSecret}"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentials);
response = await client.PostAsync(externalLoginUrl, content);
response.EnsureSuccessStatusCode();
str = await response.Content.ReadAsStringAsync();
Debug.Log("External login result");
Debug.Log(str);
var loginResult = JsonConvert.DeserializeObject<ExternalLoginResponse>(str);
return loginResult;
}
catch (Exception e)
{
Debug.Log(e.Message);
}
return null;
}
}
}.3. 上传云函数
在本地编写完成云函数之后,在 「UOS -> Func Stateless -> Open Panel」 打开的 Func Stateless 开发工具中点击上传云函数,上传完成后切换成远程调用模式,完成云函数的部署。
客户端登录并使用Passport Feature SDK
2.1 获取微信登录凭证(code)
使用微信wx.login接口获取登录凭证(code),成功获取后调用上方创建的云函数,获取玩家在Passport进行External Login后的token。
/// <summary>
/// 微信登录
/// </summary>
public static async Task<ExternalLoginResponse?> WechatLogin()
{
var tcs = new TaskCompletionSource<ExternalLoginResponse?>();
WX.Login(new LoginOption()
{
success = async (res) =>
{
var wechatApi = new WechatAPI();
Debug.Log("微信获取 code 成功");
Debug.Log(res.code);
var externalLoginResponse = await wechatApi.WechatLogin(res.code);
tcs.SetResult(externalLoginResponse);
},
fail = (err) =>
{
Debug.Log("微信获取 code 失败");
Debug.Log(err.errMsg);
tcs.SetResult(null);
}
});
return await tcs.Task;
}2.2 完整的登录流程
获取并保存token,用于后续Passport Feature的使用。
public async void Login()
{
// 初始化Passport Feature SDK
await PassportFeatureSDK.Initialize();
// 调用上方的微信登录接口,获取玩家token
var externalLoginResponse = await WechatLogin();
if (externalLoginResponse == null)
{
var exp = "微信登录失败,请稍后重试";
UIMessage.Show(exp, MessageType.Error);
throw new Exception(exp);
}
// 保存获取的token
TokenInfo tokeninfo = new TokenInfo();
tokeninfo.AccessToken = externalLoginResponse.personaAccessToken;
tokeninfo.RefreshToken = externalLoginResponse.personaRefreshToken;
tokeninfo.UserId = externalLoginResponse.persona.userID;
AuthTokenManager.SaveToken(tokeninfo);
// optional
// 可参考下方2.3部分的代码自行接入微信小游戏查询userinfo,获取玩家信息
WechatUserInfo = await GetWechatUserInfo();
var userName = WechatUserInfo?.nickName ?? "momo";
// optional
// 可更新 passport 中角色信息,包括角色名称、头像地址、自定义属性等
await PassportSDK.Identity.UpdatePersona(userName, WechatUserInfo?.avatarUrl, new Dictionary<string, string>
{
{ "key", "value" }
});
// 登录完成,可正常使用Passport Feature相关接口
}2.3 接入微信获取用户昵称头像
在微信小游戏后台配置《用户隐私保护指引》和开启隐私授权弹窗后,可以使用下方的示例代码获取到玩家的微信昵称和头像,用于更新角色信息。具体配置内容请参考微信小游戏官方文档。
/// <summary>
/// 获取微信 userinfo
/// </summary>
private Task<UserInfo> GetWechatUserInfo()
{
var tcs = new TaskCompletionSource<UserInfo>();
WX.GetSetting(new GetSettingOption()
{
fail = (err) =>
{
Debug.Log("get setting fail");
Debug.Log(err.errMsg);
tcs.SetResult(null);
},
success = (res) =>
{
if (res.authSetting.TryGetValue("scope.userInfo", out var hasUserInfo))
{
if (hasUserInfo)
{
// 已授权
WX.GetUserInfo(new GetUserInfoOption()
{
success = userInfoRes =>
{
Debug.Log("get user info success. nickname:");
Debug.Log(userInfoRes.userInfo.nickName);
tcs.SetResult(userInfoRes.userInfo);
},
fail = err =>
{
tcs.SetResult(null);
}
});
}
}
// 未授权
if (!hasUserInfo)
{
// 绘制全屏区域按钮
var button = WX.CreateUserInfoButton(0, 0, Screen.width, Screen.height, "", true);
button.OnTap((tapRes) =>
{
Debug.Log("press button");
button.Hide();
WX.GetUserInfo(new GetUserInfoOption()
{
success = userInfoRes =>
{
// 用户授权
Debug.Log("get user info success. nickname:");
Debug.Log(userInfoRes.userInfo.nickName);
tcs.SetResult(userInfoRes.userInfo);
},
fail = (err) =>
{
// 用户未授权
Debug.Log("get user info fail");
Debug.Log(err.errMsg);
tcs.SetResult(null);
}
});
});
}
},
});
return tcs.Task;
}