【第三方】微信登录

目录

  • 前言
  • 小程序登录
    • 步骤说明
    • 前端
      • 效果
      • 涉及到的接口
        • 登录凭证:wx.login
        • 获取用户信息:wx.getUserInfo
    • 后端
      • 涉及到接口
        • 小程序登录
      • 代码展示
  • 微信扫码登录

前言

微信官方文档,需要对接哪个模块就从哪里进入。
在这里插入图片描述
由于本次我们需要的是小程序的登录。所以就选择第一个。

介绍一下 API服务端
在这里插入图片描述

小程序登录

步骤说明

小程序登录步骤:

  1. 开发者客户端调用 wx.login() 获取 临时登录凭证code ,并回传到开发者服务器
  2. 开发者服务器 调用 auth.code2Session 接口,换取 用户唯一标识 OpenID用户在微信开放平台账号下的唯一标识UnionID(若当前小程序已绑定到微信开放平台账号) 和 会话密钥 session_key

如果需要的话,开发者客户端 可以调用 wx.getUserInfo ,获取到 codeencryptedData, iv,再传回给 开发者服务器,去进行解密,解密成功后就可以拿到 用户信息。

前端

效果

用户信息功能页中注明:

用户信息功能页用于帮助插件获取用户信息,包括 openid 和昵称等,相当于 wx.loginwx.getUserInfo 的功能。

在基础库版本 2.3.1 前,用户信息功能页是插件中唯一的获取 code 和用户信息的方式;

自基础库版本 2.3.1 起,用户在该功能页中进行过授权后,插件可以直接调用 wx.login 和 wx.getUserInfo:
在这里插入图片描述

涉及到的接口

登录凭证:wx.login

在这里插入图片描述

获取用户信息:wx.getUserInfo

在这里插入图片描述

后端

以Java代码为展示。

涉及到接口

小程序登录

在这里插入图片描述

代码展示

可以参考链接:https://blog.51cto.com/u_16099229/7857781

从前面可以得到,前端会返回给后端一些数据。所以,就新建一个实体类来进行接受数据。

@Data
public class WechatMPLoginParams {/*** uuid 用户uuid* code 微信返回code 用于与微信交互获取openid 等信息* encryptedData 微信返回加密信息* iv 微信返回* image 微信头像* nickname 微信用户昵称*/private String uuid, code, encryptedData, iv, image, nickName;
}

yaml配置

wechat:AppID: AppSecret: 

依赖

        <hutool-all.version>5.7.18</hutool-all.version><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>${hutool-all.version}</version></dependency>

控制层

    /*** 小程序自动登录** @param params 请求参数* @return*/@PostMapping("/login/auto-login")@ApiOperation(value = "小程序自动登录")public AjaxResult autoLogin(@RequestBody WechatMPLoginParams params) {return AjaxResult.success(connectService.miniProgramAutoLogin(params));}

业务层

@Slf4j
@Service
public class ConnectServiceImpl extends ServiceImpl<ConnectMapper, Connect> implements ConnectService {static final boolean AUTO_REGION = true;@Value("${wechat.AppID}")private String WECHAT_APPID;@Value("${wechat.AppSecret}")private String WECHAT_APPSECRET;@Override@Transactionalpublic String miniProgramAutoLogin(WechatMPLoginParams params) {HashMap cacheData = (HashMap) cache.get(CachePrefix.WECHAT_SESSION_PARAMS.getPrefix() + params.getUuid());Map<String, String> map = new HashMap<>(3);// 1. 这里最重要的是获取到 session key、unionId 、openId // 先从缓存中,查看是否已经保存了用户小程序登录的信息if (StringUtils.isEmpty(cacheData)) {// 得到微信小程序联合登陆信息,并存进缓存里面getWechatMsgByCache(params, map);} else {// 直接从缓存中获取到微信小程序联合登陆信息map = (Map<String, String>) cacheData;// System.out.println("cache: " + map.get("openId"));}// 2. 微信联合登陆return phoneMpBindAndLogin(map, params, map.get("openId"), map.get("unionId"));}/*** 微信小程序联合登陆信息并存储session key、unionId 、openId信息* * @param params 参数* @param map 联合登录信息*/private void getWechatMsgByCache(WechatMPLoginParams params, Map<String, String> map) {// System.out.println(params.getCode());JSONObject json = this.getConnect(params.getCode());// System.out.println(json);// 存储session key、unionId 、openId  后续登录用得到String sessionKey = json.getStr("session_key");String unionId = json.getStr("unionid");String openId = json.getStr("openid");map.put("sessionKey", sessionKey);map.put("unionId", unionId);map.put("openId", openId);cache.put(CachePrefix.WECHAT_SESSION_PARAMS.getPrefix() + params.getUuid(), map, 900L);}/*** 通过微信返回等code 获取openid 等信息** @param code 微信code* @return 微信返回的信息*/public JSONObject getConnect(String code) {
//        WechatConnectSettingItem setting = getWechatMPSetting();String url = "https://api.weixin.qq.com/sns/jscode2session?" +"appid=" + WECHAT_APPID + "&" +"secret=" + WECHAT_APPSECRET + "&" +"js_code=" + code + "&" +"grant_type=authorization_code";// log.info("微信返回等code 获取openid 等信息 " + url);String content = HttpUtils.doGet(url, "UTF-8", 100, 1000);// log.info(content);return JSONUtil.parseObj(content);}@Transactional(rollbackFor = Exception.class)public String phoneMpBindAndLogin(Map<String, String> map, WechatMPLoginParams params) {String sessionKey = map.get("sessionKey");String encryptedData = params.getEncryptedData();String iv = params.getIv();String unionId = map.get("unionId");String openId= map.get("openId");// 1. 解密获取用户信息JSONObject userInfo = this.getUserInfo(encryptedData, sessionKey, iv);// log.info("联合登陆返回:{}", userInfo.toString());// 2. 通过解密获得的电话号码查询是否存在该用户String phone = (String) userInfo.get("purePhoneNumber");SysUser user = new SysUser();List<SysUser> userList = sysUserService.selectUserInfoList(user);// 2.1 不存在该用户if (StringUtils.isEmpty(userList)) {throw new Exception("非员工手机号码,不允许进行微信绑定登录!");}// 2.2 存在该用户LoginUser loginUser = new LoginUser();SysUser sysUser = userList.get(0) ;BeanUtils.copyProperties(sysUser, loginUser);loginUser.setUser(sysUser);sysUser.setLoginIp(IpUtils.getIpAddr(ServletUtils.getRequest()));sysUser.setLoginDate(DateUtils.getNowDate());sysUser.setopenid(openId);sysUser.setUnionId(unionId);sysUserService.updateUserProfile(sysUser);// 3.  生成tokenreturn myTokenService.createToken(loginUser);}/*** 解密,获取微信信息** @param encryptedData 加密信息* @param sessionKey    微信sessionKey* @param iv            微信揭秘参数* @return 用户信息*/public JSONObject getUserInfo(String encryptedData, String sessionKey, String iv) {log.info("encryptedData:{},sessionKey:{},iv:{}", encryptedData, sessionKey, iv);//被加密的数据byte[] dataByte = Base64.getMimeDecoder().decode(encryptedData);//加密秘钥byte[] keyByte = Base64.getMimeDecoder().decode(sessionKey);//偏移量byte[] ivByte = Base64.getMimeDecoder().decode(iv);try {//如果密钥不足16位,那么就补足.  这个if 中的内容很重要int base = 16;if (keyByte.length % base != 0) {int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0);byte[] temp = new byte[groups * base];Arrays.fill(temp, (byte) 0);System.arraycopy(keyByte, 0, temp, 0, keyByte.length);keyByte = temp;}//初始化Security.addProvider(new BouncyCastleProvider());Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");parameters.init(new IvParameterSpec(ivByte));//初始化cipher.init(Cipher.DECRYPT_MODE, spec, parameters);byte[] resultByte = cipher.doFinal(dataByte);if (null != resultByte && resultByte.length > 0) {String result = new String(resultByte, StandardCharsets.UTF_8);return JSONUtil.parseObj(result);}} catch (Exception e) {log.error("解密,获取微信信息错误", e);}throw new ServiceException("联合第三方登录,授权信息错误", 20012);}
}    

功能类

package com.higentec.system.common.utils.http;import java.io.*;
import java.net.*;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.higentec.system.common.constant.Constants;
import com.higentec.system.common.utils.StringUtils;/*** 通用http发送方法* * @author higentec*/
public class HttpUtils
{private static final Logger log = LoggerFactory.getLogger(HttpUtils.class);/*** 向指定 URL 发送GET方法的请求** @param url 发送请求的 URL* @return 所代表远程资源的响应结果*/public static String sendGet(String url){return sendGet(url, StringUtils.EMPTY);}/*** 向指定 URL 发送GET方法的请求** @param url 发送请求的 URL* @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。* @return 所代表远程资源的响应结果*/public static String sendGet(String url, String param){return sendGet(url, param, Constants.UTF8);}/*** 向指定 URL 发送GET方法的请求** @param url 发送请求的 URL* @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。* @param contentType 编码类型* @return 所代表远程资源的响应结果*/public static String sendGet(String url, String param, String contentType){StringBuilder result = new StringBuilder();BufferedReader in = null;try{String urlNameString = StringUtils.isNotBlank(param) ? url + "?" + param : url;log.info("sendGet - {}", urlNameString);URL realUrl = new URL(urlNameString);URLConnection connection = realUrl.openConnection();connection.setRequestProperty("accept", "*/*");connection.setRequestProperty("connection", "Keep-Alive");connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");connection.connect();in = new BufferedReader(new InputStreamReader(connection.getInputStream(), contentType));String line;while ((line = in.readLine()) != null){result.append(line);}log.info("recv - {}", result);}catch (ConnectException e){log.error("调用HttpUtils.sendGet ConnectException, url=" + url + ",param=" + param, e);}catch (SocketTimeoutException e){log.error("调用HttpUtils.sendGet SocketTimeoutException, url=" + url + ",param=" + param, e);}catch (IOException e){log.error("调用HttpUtils.sendGet IOException, url=" + url + ",param=" + param, e);}catch (Exception e){log.error("调用HttpsUtil.sendGet Exception, url=" + url + ",param=" + param, e);}finally{try{if (in != null){in.close();}}catch (Exception ex){log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);}}return result.toString();}/*** 向指定 URL 发送POST方法的请求** @param url 发送请求的 URL* @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。* @return 所代表远程资源的响应结果*/public static String sendPost(String url, String param){PrintWriter out = null;BufferedReader in = null;StringBuilder result = new StringBuilder();try{String urlNameString = url;log.info("sendPost - {}", urlNameString);URL realUrl = new URL(urlNameString);URLConnection conn = realUrl.openConnection();conn.setRequestProperty("accept", "*/*");conn.setRequestProperty("connection", "Keep-Alive");conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");conn.setRequestProperty("Accept-Charset", "utf-8");conn.setRequestProperty("contentType", "utf-8");conn.setDoOutput(true);conn.setDoInput(true);out = new PrintWriter(conn.getOutputStream());out.print(param);out.flush();in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));String line;while ((line = in.readLine()) != null){result.append(line);}log.info("recv - {}", result);}catch (ConnectException e){log.error("调用HttpUtils.sendPost ConnectException, url=" + url + ",param=" + param, e);}catch (SocketTimeoutException e){log.error("调用HttpUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + param, e);}catch (IOException e){log.error("调用HttpUtils.sendPost IOException, url=" + url + ",param=" + param, e);}catch (Exception e){log.error("调用HttpsUtil.sendPost Exception, url=" + url + ",param=" + param, e);}finally{try{if (out != null){out.close();}if (in != null){in.close();}}catch (IOException ex){log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);}}return result.toString();}public static String sendSSLPost(String url, String param){StringBuilder result = new StringBuilder();String urlNameString = url + "?" + param;try{log.info("sendSSLPost - {}", urlNameString);SSLContext sc = SSLContext.getInstance("SSL");sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom());URL console = new URL(urlNameString);HttpsURLConnection conn = (HttpsURLConnection) console.openConnection();conn.setRequestProperty("accept", "*/*");conn.setRequestProperty("connection", "Keep-Alive");conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");conn.setRequestProperty("Accept-Charset", "utf-8");conn.setRequestProperty("contentType", "utf-8");conn.setDoOutput(true);conn.setDoInput(true);conn.setSSLSocketFactory(sc.getSocketFactory());conn.setHostnameVerifier(new TrustAnyHostnameVerifier());conn.connect();InputStream is = conn.getInputStream();BufferedReader br = new BufferedReader(new InputStreamReader(is));String ret = "";while ((ret = br.readLine()) != null){if (ret != null && !"".equals(ret.trim())){result.append(new String(ret.getBytes("ISO-8859-1"), "utf-8"));}}log.info("recv - {}", result);conn.disconnect();br.close();}catch (ConnectException e){log.error("调用HttpUtils.sendSSLPost ConnectException, url=" + url + ",param=" + param, e);}catch (SocketTimeoutException e){log.error("调用HttpUtils.sendSSLPost SocketTimeoutException, url=" + url + ",param=" + param, e);}catch (IOException e){log.error("调用HttpUtils.sendSSLPost IOException, url=" + url + ",param=" + param, e);}catch (Exception e){log.error("调用HttpsUtil.sendSSLPost Exception, url=" + url + ",param=" + param, e);}return result.toString();}private static class TrustAnyTrustManager implements X509TrustManager{@Overridepublic void checkClientTrusted(X509Certificate[] chain, String authType){}@Overridepublic void checkServerTrusted(X509Certificate[] chain, String authType){}@Overridepublic X509Certificate[] getAcceptedIssuers(){return new X509Certificate[] {};}}/*** get 请求 静态方法** @param link* @param encoding* @return*/public static String doGet(String link, String encoding, int connectTimeout, int readTimeout) {HttpURLConnection conn = null;try {URL url = new URL(link);conn = (HttpURLConnection) url.openConnection();conn.setRequestMethod("GET");conn.setConnectTimeout(connectTimeout);conn.setReadTimeout(readTimeout);BufferedInputStream in = new BufferedInputStream(conn.getInputStream());ByteArrayOutputStream out = new ByteArrayOutputStream();byte[] buf = new byte[1024];for (int i = 0; (i = in.read(buf)) > 0; ) {out.write(buf, 0, i);}out.flush();String s = out.toString(encoding);return s;} catch (Exception e) {throw new RuntimeException(e.getMessage(), e);} finally {if (conn != null) {conn.disconnect();conn = null;}}}private static class TrustAnyHostnameVerifier implements HostnameVerifier{@Overridepublic boolean verify(String hostname, SSLSession session){return true;}}
}

如果需要单独从微信小程序获取用户手机号码,可参考:

  • 微信开发文档—— 获取手机号
  • 详细教程:微信小程序获取用户手机号码教程(前端+后端):https://blog.csdn.net/qq_51235856/article/details/131158254

微信扫码登录

微信登录——授权登录获取用户信息:https://blog.csdn.net/YXXXYX/article/details/127338450

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/200035.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

有趣的代码——有故事背景的程序设计3

这篇文章再和大家分享一些有“背景”的程序设计&#xff0c;希望能够让大家学到知识的同时&#xff0c;对编程学习更感兴趣&#xff0c;更能在这条路上坚定地走下去。 目录 1.幻方问题 2.用函数打印九九乘法表 3.鸡兔同笼问题 4.字数统计 5.简单选择排序 1.幻方问题 幻方又…

【无标题】什么是UL9540测试,UL9540:2023版本增加哪些测试项目

什么是UL9540测试&#xff0c;UL9540:2023版本增加哪些测试项目 UL 9540是美国安全实验室&#xff08;Underwriters Laboratories&#xff09;发布的标准&#xff0c;名称为"UL 9540: Energy Storage Systems and Equipment"&#xff0c;翻译为中文为"能量存储…

【JavaWeb】前端工程化(VUE3)

前端工程化&#xff08;VUE3&#xff09; 文章目录 前端工程化&#xff08;VUE3&#xff09;一、概述二、ECMA6Script2.1 es6的变量和模板字符串2.2 es6的解构表达式2.3 es6的箭头函数2.4 rest和spread2.5 es6的对象创建和拷贝2.6 es6的模块化处理 三、前端工程化环境搭建3.1 N…

数据分析师的学习之路-pandas篇(7)

继续接上篇&#xff0c;这次学习下透视表、线性回归还有根据条件上颜色。 3.9 透视表 在excel里也经常用到透视表来构建想要的列的组合来形成一个新的表&#xff0c;在pandas里也能做。 举例数据是这样的&#xff1a; 是各种类产品的订单数据&#xff0c;现在想做一个透视表&…

electron 应用图标修改

修改窗口图标 更换Electron应用程序的桌面图标 准备好你想要作为图标的图片文件&#xff0c;可以是PNG格式安装一个可以转换图片格式为ICO的工具&#xff0c;例如在线转换工具“在线转换icon图标工具”。将你的PNG图片文件上传并转换为ICO格式将转换得到的ICO文件放到你的El…

synxflow 安装环境

介绍&#xff1a; 该软件可以动态模拟洪水淹没&#xff0c;滑坡跳动和泥石流使用多个cuda支持的gpu。它还提供了一个用户友好但多功能的Python界面&#xff0c;可以完全集成到数据科学工作流程中&#xff0c;旨在简化和加速危害风险评估任务。 这个包我从网上找到的资源特别特…

GAN:WGAN-DIV

论文&#xff1a;https://arxiv.org/pdf/1712.01026.pdf 代码&#xff1a; 发表&#xff1a;2018 摘要 在计算机视觉的许多领域中&#xff0c;生成对抗性网络已经取得了巨大的成功&#xff0c;其中WGANs系列被认为是最先进的&#xff0c;主要是由于其理论贡献和竞争的定性表…

11、信息打点——红队工具篇FofaQuakeSuize水泽Arl灯塔

网络空间测绘引擎 Fofa Quake shodan Zoomeye 主要搜关联资产、特征资产、资产信息&#xff08;在测绘引擎上直接搜IP&#xff0c;它会显示所有与该域名有关的信息。&#xff09; fofa和Quake测绘引擎集成化工具&#xff1a;Finger 自动化信息收集项目 ARL灯塔 Suize水泽 …

洗地机好用吗?口碑好的洗地机有哪些?

自从洗地机开始引入市场以来&#xff0c;它一直受到人们的关注。它在解放家庭清洁劳动力和提供快速方便的清洁方面表现出色&#xff0c;超越了多年来传统的拖把清洁方式。越来越多的人选择使用洗地机来完成家庭清洁任务。如果你也对洗地机产生了浓厚的兴趣&#xff0c;并想购买…

在 AlmaLinux 9.2 上安装Oracle Database 23c

在 AlmaLinux 9.2 上安装Oracle Database 23c 1. 安装 Oracle Database 23c2. 连接 Oracle Database 23c3. 重启启动后&#xff0c;手动启动数据库4. 重启启动后&#xff0c;手动启动 Listener5. 手动启动 Pluggable Database6. 自动启动 Pluggable Database7. 设置开机启动数据…

【面试】Java最新面试题资深开发-JVM第一弹

问题一&#xff1a;Java中的垃圾回收机制 在Java中&#xff0c;垃圾回收是如何工作的&#xff0c;可以简要描述一下垃圾回收的算法有哪些吗&#xff1f; 在Java中&#xff0c;垃圾回收是一种自动管理内存的机制&#xff0c;它负责识别不再被程序引用的对象并释放其占用的内存…

Linux(11):Linux 账号管理与 ACL 权限设定

Linux 的账号与群组 每个登入的使用者至少都会取得两个 ID&#xff0c;一个是使用者 ID(User ID &#xff0c;简称UID)、一个是群组ID (Group ID &#xff0c;简称GID)。 Linux系统上面的用户如果需要登入主机以取得 shell 的环境来工作时&#xff0c;他需要如何进行呢? 首先…

测绘资质测绘设备检定、校准管理制度

测绘设备检定、校准管理制度 建立健全测绘仪器设备检定、校准管理制度&#xff0c;明确测绘仪器设备的检定、校准、日常管理等要求

Unity环境配置并解决visual studio 不能智能代码提示Unity代码问题(一)

1、请先安装好unity和Visual Studio 2019 2、Visual Studio需要安装如图&#xff08;2019才会有那个移动的可以勾选&#xff09; 3、Unity配置 file->build setting windows->package manager 安装如下图 edit->preferences 3、创建c#脚本 如果还是没能智能提…

Centos7上安装Redis

第一步&#xff1a;安装Redis依赖 yum install -y gcc tcl //需要使用管理员权限第二步&#xff1a;下载上传安装包并解压 下载地址redis中文官网 上传成功后解压 输入tar -zxvf &#xff08;redis版本&#xff09;,即可解压成功 进入redis目录&#xff0c;运行编译命令&am…

安全AI系统开发指南

执行摘要 本文件建议为使用人工智能&#xff08;AI&#xff09;的任何系统的提供商提供指导方针&#xff0c;无论这些系统是从头开始创建的&#xff0c;还是建立在他人提供的工具和服务之上的。实施这些指导方针将有助于提供商构建按预期运行、在需要时可用的人工智能系统&…

CTF特训日记day(4-6)

来复现一下2022QWB决赛的RDP题目 这两天腰疼去了趟医院 题目要求我们攻击XRDP程序&#xff0c;从而达到本地提权的效果。 首先观察XRDP程序的版本信息 rootRDP:/home/rdp/Desktop# xrdp-sesman -version xrdp-sesman 0.9.18The xrdp session managerCopyright (C) 2004-2020…

【eNSP实践】eNSP实战篇(2)之简单实现交换机与主机的配置(图文详解)

目录 写在前面涉及知识1、交换机实验1.1 实验条件1.2 实验步骤A、打开eNSP软件&#xff0c;创建拓扑B、搭建主机与交换机连线C、配置交换机和主机D、验证不同网段设备可通性 1.3 通过交换机查看MAC地址 写在最后 写在前面 其实前面文章我有介绍关于路由器的使用&#xff0c;但…

Java多线程技术二:线程间通信——wait/notify机制

1 概述 线程时操作系统中独立的个体&#xff0c;但这些个体如果不经过特殊的处理是不能成为一个整体的。线程间的通信就是使线程成为整体的比用方案之一&#xff0c;可以说&#xff0c;是线程间进行通信后系统之间的交互性会更强大&#xff0c;CPU利用率会得以大幅提高&#xf…

执法记录仪、一体化布控球等目前支持的AI智能算法、视频智能分析算法有哪些

一、前端设备实现AI算法 主要是基于安卓的布控球实现&#xff0c;已有的算法包括&#xff1a; 1&#xff09;人脸&#xff1b;2&#xff09;车牌&#xff1b;3&#xff09;是否佩戴安全帽&#xff1b;4&#xff09;是否穿着工装&#xff1b; 可以支持定制开发 烟雾&#xf…