uni-app 多端登录解决方案

本贴最后更新于 950 天前,其中的信息可能已经渤澥桑田

uni-app 是一个跨端开发的前端框架,可以使用一套代码同时开发 IOS、安卓、小程序的前端,是快速开发移动应用的不错的选择。

在现实应用场景中,一个用户在使用当前移动应用时可能会用 IOS、安卓或小程序的任意一种。大部分情况下,这个用户无论在使用何种设备环境下,他都是唯一标识的用户,设备分区那些应用除外(例如腾讯的部分游戏)。

现有方案

那么如何标识出一个用户在不同设备环境下均为同一个用户呢? 通常有以下几种方案:

  1. 使用用户名和密码作为登录系统:该方案需要用户主动注册并设置用户名和密码,那么他在不同设备环境下,使用自己账户登录即是唯一的。
  2. 使用第三方登录作为登录系统:例如微信授权登录,在 IOS 和安卓应用中均可以实现跳转微信授权,而小程序端如果只是微信小程序,那么在同一主体下理论上可以通过 unionid 标识出该用户,但支付宝或其他类型的小程序则无法应用该方案。
  3. 使用手机号作为登录系统:用户手机号可以作为唯一标识用户的方法,且使用手机号授权的方式,会比方案一使用账号密码的方案更加方便。

综上分析,第三种方案可能是最为普遍的方案,本文会以第三种方案使用手机号授权为例,来详细地阐述该方案的实行过程。

IOS、安卓一键登录

APP 端在使用手机号授权时会比较方便,因为 uni-app 提供了一键登录方案,可以按照官方给出的 一键登录文档 进行开发,笔者在该过程中排了一些坑,可以按照下文操作。

登录界面

第一步,最好是能先把这个一键登录的界面先能实现出来

文档中也提供了 uni.login() 方式即可

uni.login({
	provider: 'univerify',
	univerifyStyle: { 
    		fullScreen: true
	}
})

可以通过修改 univerifyStyle 属性个性化定制页面,具体也可以参考文档中 univerifyStyle 数据结构 部分。

authApp() {
	uni.login({
		provider: 'univerify',
		univerifyStyle: {
			fullScreen: true,
			otherLoginButton: {
				visible: false
			},
			icon: {
				path: "static/logo.png"			},
		},
		success: res => {
			console.log(res)
			if (res.errMsg === 'login:ok') {
				// 登录处理
			}
		}
	})
}

云函数换取手机号

在文档中可以发现,其提供了三种方式换取手机号,在点击授权后会收到一个 access_token 和一个 openid,此时需要使用 access_token 换取手机号。

image.png

简单地讲,它有三种换取手机号的方案:

  1. 前端单纯使用云函数调用
  2. 前端将参数 access_token 提交给云函数
  3. 前端将参数 access_token 提交给自己的服务器,自己的服务器再调用云函数

这三种方案,可以发现一个共同的特点,就是没法离开云函数,开始笔者也想找其他方案,但未果。

综合考虑官方给出的三种方案,如果不实用其提供的云数据库的话,第 3 种方案会是一个好的选择,但官方还提到了 URL 化,实测其实可以不需要

开发者配置

写云函数之前需要先进入 开发者中心 进行配置。

首先需要创建一个应用,如果你的应用已经创建则这部无需重复创建。

image.png

开通一键登录,开通后会生成 ApiKeyApiSecret,后续在云开发时会使用到。

顺便再添加应用,这部分是在打包后需要用到的,一般在生成签名时需要注意,可以参考 Android 签名证书生成 ,这个比较详细,按他的方案下来基本可以成功。

image.png

云函数配置及编写

右键项目创建云开发环境

image.png

创建后再右键关联云服务空间

image.png

点击新建

image.png

跳转到云服务空间后点击创建

image.png

返回到 IDE 中,选择刚刚创建的云空间并关联

image.png

新建一个云函数

image.png

按如下代码编写一个换取手机号的云函数,此处没有按照官方的方式使用 URL 化,因为云函数是由服务器后端接口去调用的,不对外暴露,无需再做其他复杂的操作。

修改 appidapiKeyapiSecret

'use strict';

exports.main = async (event) => {
	const params = event.queryStringParameters
	const {
		access_token,
		openid
	} = params
	const res = await uniCloud.getPhoneNumber({
		provider: 'univerify',
		appid: '__UNI__xxx',
		apiKey: 'xxx',
		apiSecret: 'xxx',
		access_token: access_token,
		openid: openid
	})
	return res
}

上传写好的云函数

image.png

进入云函数控制台,配置这个云函数的路径,自定义即可。

image.png

最后可以测试一下, uni.login() 后会打印出参数,之后将参数通过 PostMan 传递给你的云函数,如果成功返回手机号,则可以继续下面的步骤了。

Java 后端调用云函数

后端接受前端传来的,access_tokenopenid,之后通过后端去调用云函数后,最后可以将手机号和 openid 进行用户信息入库。

入库后,后续的登录直接可以通过 openid 去查找用户,因为 openid 是唯一的,无需再调用云函数换取手机号,毕竟换一次需要 2 分钱呢。

以 Java 为例,调用接口可以使用 RestTemplateHttpClient 或其他工具,笔者用的是一个开源工具 http-request 属实非常的方便。

@Component
public class UniCloud {
	@Value("${unicloud-url}")
	private String uniCloudUrl;

	/**
	 * UniCloud 获取手机号
	 */
	public String getPhoneNumber(String accessToken, String openid) {
		String url = this.uniCloudUrl + "auth?access_token=" + accessToken + "&openid=" + openid;
		String res = HttpRequest.get(url).body();
		Gson gson = new Gson();
		JsonObject jsonObject = gson.fromJson(res, JsonObject.class);
		if (jsonObject.has("success") && jsonObject.get("success").getAsBoolean()) {
			return jsonObject.get("phoneNumber").getAsString();
		}		
		throw new UniCloudAPIError();
	}
}

最后还有一点需要注意,后面是使用小程序的方案登录,如果在通过上述 APP 的 openid 没有找到用户时,还是需要换取一次手机号的,因为这个用户可能已经通过小程序注册了,并且在这一次请求中把 APP 的 openid 也入库。

小程序手机号登录

在结束了 APP 一键登录授权后,还需要实现一下小程序的手机号登录,因为一键登录,仅仅适用于 APP,不适用于小程序。

登录界面

类似的可以做一个和一键登录差不多的页面,此处按钮需要绑定获取手机号的事件。

调用后端接口

此接口我还会传一个 openidCode,这个 openidCode 其实就是小程序 login() 后返回的 code,一并传到后端可以获得用户的 openid

export default {
	data() {
		return {
			appName: null,
			openidCode: null
		}
	},
	onShow() {
		uni.login({
			success: res => {
				this.openidCode = res.code
			}
		})
	},
	methods: {
		getphonenumber(e) {
			uni.showLoading({
				title: '正在授权中...'
			})
			const data = {
				'code': e.detail.code,
				'openid_code': this.openidCode
			}
			// 此处使用 uni.request 返送请求到后端,传的参数为 data
		}
	}
}

Java 后端换取手机号和 openid

小程序后端换取手机号和 openid 的方法比较多,这里推荐使用 WxJava 一个可以直接调用微信小程序、微信公众号等 API 接口的工具,也是相当好用。

@Component
public class WechatMp {
	private final WxMaService wxMaService;

	@Autowired
	public WechatMp(WxMaService wxMaService) {
		this.wxMaService = wxMaService;
	}

	/**
	 * 获取手机号
	 */
	public String getPhoneNumber(String code) {
		try {
			WxMaPhoneNumberInfo newPhoneNoInfo = this.wxMaService.getUserService().getNewPhoneNoInfo(code);
			return newPhoneNoInfo.getPurePhoneNumber();
		} catch (WxErrorException e) {
			throw new WxAPIError();
		}
	}

	/**
	 * 获取openid
	 */
	public String getOpenid(String code) {
		try {
			return this.wxMaService.getUserService().getSessionInfo(code).getOpenid();
		} catch (WxErrorException e) {
			throw new WxAPIError();
		}
	}
}

最后,就写到这里吧,过程中还省略了前端发送请求,后端接受请求的方法,可以自行根据业务补充。

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...