【外部服务对接】对接Firebase支持谷歌、Facebook、苹果等第三方平台用户注册登录

news2025/1/16 17:53:47

【外部服务对接】对接Firebase支持谷歌、Facebook、苹果等第三方平台登录

背景

因主要做国外尼日的市场,相关的应用的全是国外用户使用,为了方便用户的注册和登录,接入国外的统一平台Firebase,集成使用很方便。

主要步骤

1.注册登录Firebase 平台
1.1 注册google账号进入firebase后台,创建项目

在这里插入图片描述

1.2 在项目下创建应用,根据实际情况可选,安卓、ios、web类型。在这里插入图片描述
1.3 生成自己项目的私钥文件,下载保存到java应用中

在这里插入图片描述

2.引入Maven jar 包依赖
 <dependency>
    <groupId>com.google.firebase</groupId>
    <artifactId>firebase-admin</artifactId>
    <version>9.1.1</version>
 </dependency>
3.第三方注册登录授权表
CREATE TABLE `user_login_authorize` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `user_id` varchar(32) NOT NULL COMMENT '用户id',
  `email` varchar(255) DEFAULT NULL COMMENT '邮箱',
  `identity_type` varchar(20) NOT NULL COMMENT '登录类型:  google/facebook',
  `identifier` varchar(255) NOT NULL COMMENT '手机号/邮箱/第三方唯一标识',
  `credential` longtext COMMENT '第三方的token',
  `create_datetime` datetime DEFAULT NULL COMMENT '创建时间',
  `update_datetime` datetime DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_identifier` (`identifier`) USING BTREE COMMENT '唯一键'
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='用户授权信息表';
4.Firebase注册登录的核心业务实现
4.1 FireBaseService
package com.ogc.standard.firebase;

import com.alibaba.fastjson.JSONObject;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.common.collect.Lists;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseAuthException;
import com.google.firebase.auth.FirebaseToken;
import com.google.firebase.auth.UserRecord;
import com.google.firebase.messaging.*;
import com.ogc.standard.enums.EEnv;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static com.ogc.standard.common.PropertiesUtil.Config.ENV;


@Slf4j
@Service
public class FireBaseService {

    /**
     * 如果设备处于离线状态,消息应在 FCM 存储中保留多长时间(以秒为单位)。
     * 支持的最长存活时间为 4 周,如果不设置则默认值为 4 周。如果要立即发送消息,请将其设置为 0。
     * 在 JSON 格式中,Duration 类型被编码为字符串而不是对象,其中字符串以后缀“s”(表示秒)结尾,前面是秒数,纳秒表示为小数秒。
     * 例如3秒0纳秒,JSON格式编码为“3s”,3秒1纳秒JSON格式编码为“3.000000001s”。 ttl 将向下舍入到最接近的秒数。
     * 以秒为单位的持续时间,最多九个小数位,以 ' s ' 结尾。示例: "3.5s" 。
     */
    @Value("${firebase.messaging.android.ttl:}")
    private Long androidTtl;

    /**
     * 消息优先级。可以取“正常”和“高”值
     */
    @Value("${firebase.messaging.android.priority:}")
    private String androidPriority;

    /**
     * 默认为10,立刻发送推送消息
     */
    @Value("${firebase.messaging.apns.priority:}")
    private String apnsPriority;

    /**
     * 发送次数
     */
    @Value("${firebase.messaging.apns.aps.badge:}")
    private Integer apnsApsBadge;

    private static FirebaseApp firebaseApplication;


    static  {
        //firebase初始化
        ClassPathResource classPathResource = new ClassPathResource("tbay-5c74c-firebase-adminsdk-1kj38-64818b9446.json");

        InputStream inputStream = null;
        FirebaseOptions options = null;
        try {
            inputStream = classPathResource.getInputStream();
            options = FirebaseOptions.builder()
                    .setCredentials(GoogleCredentials.fromStream(inputStream))
                    .build();
        } catch (IOException e) {
            log.error("firebase初始化初始化失败,原因:{}", e.getMessage(), e);
        }

        firebaseApplication = FirebaseApp.initializeApp(options);
    }


    public Boolean verifyTokenId(String idToken, String email) {
        if (ENV.equals(EEnv.DEV.getCode())) {
            System.setProperty("https.proxyHost", "10.0.84.188");
            System.setProperty("https.proxyPort", "7890");
            System.setProperty("com.google.api.client.should_use_proxy", "true");
        }
        FirebaseToken decodedToken = null;
        try {
            decodedToken = FirebaseAuth.getInstance().verifyIdToken(idToken);
        } catch (FirebaseAuthException e) {
            log.error("firebase id token 解析失败 {}", e.getMessage(), e);
        }
        return decodedToken != null && decodedToken.getEmail().equalsIgnoreCase(email);
    }

    public Boolean verifyTokenIdAndUid(String idToken, String uid) {
        if (ENV.equals(EEnv.DEV.getCode())) {
            return true;
        }
        FirebaseToken decodedToken = null;
        try {
            decodedToken = FirebaseAuth.getInstance().verifyIdToken(idToken);
        } catch (FirebaseAuthException e) {
            log.error("firebase id token 解析失败 {}", e.getMessage(), e);
        }
        return decodedToken != null && decodedToken.getUid().equals(uid);
    }

    /**
     * 获取用户基本信息
     *
     * @param uid
     * @return
     */
    public static UserRecord getUserById(String uid) {
        UserRecord userRecord = null;
        try {
            userRecord = FirebaseAuth.getInstance().getUser(uid);
        } catch (FirebaseAuthException e) {
            log.error(e.getMessage(), e);
        }
        return userRecord;
    }

    /**
     * 给指定的用户发送推送消息
     *
     * @param req 请求参数
     */
    public void sendMessage(FirebaseMessagingReq req) {
        //开发和测试环境需要自己配置代理访问外网
        if (!runtimeEnv.isProd()){
            System.setProperty("https.proxyHost", "10.0.84.188");
            System.setProperty("https.proxyPort", "7890");
        }

        Asserts.isTrue(StrUtil.isEmpty(req.getTitle()), "param title should not be null");
        Asserts.isTrue(StrUtil.isEmpty(req.getBody()), "param body should not be null");
        Asserts.isTrue(Objects.isNull(req.getUserIds()), "param userIds should not be null");
        // 查询用户的firebase的token,用户信息表需要保存idToken
        List<UmsMemberResponse> umsMemberResponseList = umsMemberInfoService.findByUserIds(req.getUserIds());
        // 如果token为空,不发送
        if (CollUtil.isEmpty(umsMemberResponseList)) {
            return;
        }

        List<Message> androidMessageList = Lists.newArrayList();
        List<Message> apnsMessageList = Lists.newArrayList();
        umsMemberResponseList.forEach(item -> {
            String token = item.getFirebaseToken();
            if (StrUtil.isNotEmpty(token)) {
                req.setToken(token);
                Map<String, String> data = new HashMap<>();
                if (Objects.nonNull(req.getBizType())) {
                    JSONObject jsonObject = new JSONObject();
                    jsonObject.put("bizType", req.getBizType());
                    jsonObject.put("bizData", req.getBizData());
                    jsonObject.put("imageUrl", req.getImageUrl());
                    data.put("data", jsonObject.toJSONString());
                }
                if ("ios".equals(item.getFirebaseTokenSource())) {
                    // 组装ios推送消息
                    Message apnsMessage = apnsMessage(req, data);
                    apnsMessageList.add(apnsMessage);
                } else {
                    // 组装android推送消息
                    Message androidMessage = androidMessage(req, data);
                    androidMessageList.add(androidMessage);
                }
            }
        });

        // 发送推送消息
        try {
            if (CollUtil.isNotEmpty(androidMessageList)) {
                int sendCount = send(androidMessageList);
                log.info("Successfully sent android messages, count: {}", sendCount);
            }

            if (CollUtil.isNotEmpty(apnsMessageList)) {
                int sendCount = send(apnsMessageList);
                log.info("Successfully sent ios messages, count: {}", sendCount);
            }
        } catch (Exception e) {
            log.error("send firebase messages failed:{}", e.getMessage(), e);
        }
    }

    public int send(List<Message> messageList) throws FirebaseMessagingException {
        int index = 0;
        int sendCount = 0;
        while (index < messageList.size()) {
            int endIndex = index + 499;
            //分批插入消息
            BatchResponse response = FirebaseMessaging.getInstance(firebaseApplication).sendAll(messageList.subList(index, Math.min(endIndex, messageList.size())));
            sendCount += response.getSuccessCount();
            index += 500;
        }
        return sendCount;
    }

    public Message androidMessage(FirebaseMessagingReq req, Map<String, String> data) {
        return Message.builder()
                .putAllData(data)
                .setAndroidConfig(AndroidConfig.builder()
                        .setTtl(androidTtl) // 1 hour in milliseconds
                        .setPriority("NORMAL".equals(androidPriority) ? AndroidConfig.Priority.NORMAL : AndroidConfig.Priority.HIGH)
                        .setNotification(AndroidNotification.builder()
                                .setTitle(req.getTitle())
                                .setBody(req.getBody())
                                .setImage(req.getImageUrl())
                                .build())
                        .build())
                .setToken(req.getToken())
                .build();
    }

    public Message apnsMessage(FirebaseMessagingReq req, Map<String, String> data) {
        String value = "1";
        ApnsFcmOptions.Builder builder = ApnsFcmOptions.builder().setImage(req.getImageUrl());
        return Message.builder()
                .putAllData(data)
                .setApnsConfig(
                        ApnsConfig.builder()
                                .putHeader("apns-priority", apnsPriority)
                                .putHeader("mutable-content", value)
                                .setFcmOptions(builder.build())
                                .setAps(Aps.builder()
                                        .setAlert(ApsAlert.builder()
                                                .setTitle(req.getTitle())
                                                .setBody(req.getBody())
                                                .build())
                                        .setBadge(apnsApsBadge)
                                        .build())
                                .build())
                .setToken("Bearer " + req.getToken())
                .build();
    }

    /**
     * firebase 消息体
     */
    @Data
    class FirebaseMessagingReq {
        /**
         * 标题
         */
        private String title;
        /**
         * 消息实体
         */
        private String body;
        /**
         * 用户id
         */
        private List<Long> userIds;
        /**
         * 用户token
         */
        private String token;
        /**
         * 业务类型 1:订单状态变更 2:售后状态变更 3:提现状态变更 4:转账记录状态变更
         */
        private Integer bizType;
        /**
         * 业务数据
         */
        private JSONObject bizData;
        /**
         * 图片地址
         */
        private String imageUrl;
    }
}
4.2 UmsSsoServiceImpl

    /**
     * 第三方登录方法
     * @param  Request
     *   email//邮箱
     *   idToken //token
     *   uid//唯一键
     *   bindEmail//绑定邮箱
     *   identityType//登录类型  google/facebook
     *   captcha//验证码
     *   inviteCode//推荐人手机号
     *   nickname//昵称
     *   photo//头像
     * @return 用户ID
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String doThirdLogin(Request request) {
        String thirdUid = request.getUid();
        String idToken = request.getIdToken();
        UserLoginAuthorize userLoginAuthorize = null;

        // 验证token是否有效
        Boolean verifyToken = fireBaseService.verifyTokenIdAndUid(idToken, request.getUid());
        if(!verifyToken){
            throw new BizException(SUS00016.getCode(),SUS00016.getInfo());
        }

        // 根据第三方ID查询用户登录授权信息
        if(StringUtils.isNotBlank(request.getUid())){
            userLoginAuthorize  = iUserLoginAuthorizeAO.findByIdentifier(thirdUid);
        }

        // 根据邮箱查询用户登录授权信息
        if(userLoginAuthorize == null && StringUtils.isNotBlank(request.getEmail())){
            List<UserLoginAuthorize> authorizes = iUserLoginAuthorizeAO.findByEmail(request.getEmail(), null);
            if(CollectionUtils.isNotEmpty(authorizes)){
                List<UserLoginAuthorize> collect = authorizes.stream().filter(s -> s.getIdentityType().equals(request.getIdentityType())).collect(Collectors.toList());
                if(CollectionUtils.isNotEmpty(collect)){
                    userLoginAuthorize = collect.get(0);
                }
            }else{
                User user = userBO.getUserByEmail(request.getEmail(), request.getSubsidiaryCode());
                if(user != null){
                    userLoginAuthorize = new UserLoginAuthorize();
                    userLoginAuthorize.setUserId(user.getUserId());
                }
            }
        }

        // 根据绑定邮箱查询用户登录授权信息
        if(userLoginAuthorize == null && StringUtils.isNotBlank(request.getBindEmail())){
            User user = userBO.getUserByEmail(request.getBindEmail(), request.getSubsidiaryCode());
            if(user != null){
                userLoginAuthorize = new UserLoginAuthorize();
                userLoginAuthorize.setUserId(user.getUserId());
            }
        }

        String userId = "";
        if(userLoginAuthorize == null){
            // 调用系统原进行注册接口
        }else{
            // 调用系统原登录接口
        }
        //新增或者更新 第三方登录授权表信息
        iUserLoginAuthorizeAO.addOrUpdate(request,userId);
        return userId;
    }

注意

1.不同平台的同一个邮箱号注册登录的兼容情况
2.里面还有应用外消息推送的代码,可以针对用户手机推送业务消息

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

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

相关文章

如何提高小红书笔记的互动率

相信有很多新手在运营小红书的时候&#xff0c;可能都会遇到过以下这样的情况&#xff1a; 笔记点赞、收藏数据明明还可以&#xff0c;但评论区却没有人留言&#xff1f;为何大家只给点赞、收藏&#xff0c;却不关注账号&#xff1f; 其实&#xff0c;这背后有很多运营技巧&a…

​做好研发管理的三个条件​

1.制造鼓励创新的环境 要做好研发管理&#xff0c;首先要制造一个鼓励创新、适合研发的环境&#xff0c;必须采取弹性而目标化的管理&#xff0c;不以死板的制度限制员工的创意&#xff0c;必须要求实质的成果。 2.融入行销观念 将行销的观念融入研发中&#xff1a;为使有限的…

xlua游戏热更新(lua访问C#)

CS.UnityEngine静态方法访问unity虚拟机 创建游戏物体 CS.UnityEngine.GameObject(new by lua);静态属性 CS.UnityEngine.GameObject(new by lua); -- 创建 local camera CS.UnityEngine.GameObject.Find(Main Camera); --查找 camera.name Renamed by Lua;访问组件 loca…

通配符匹配

题目链接 通配符匹配 题目描述 注意点 s 仅由小写英文字母组成p 仅由小写英文字母、‘?’ 或 ‘*’ 组成‘?’ 可以匹配任何单个字符‘*’ 可以匹配任意字符序列&#xff08;包括空字符序列&#xff09; 解答思路 最初想到的是dfs 剪枝&#xff0c;但是用例超时了参照题…

小型企业如何数字化转型?ZohoCRM助力小企业转型

小型企业数字化之路倍加艰难&#xff0c;其组织规模有限、资源有限&#xff0c;数字化布局或转型&#xff0c;也存在与数字平台匹配度的问题。其实小型企业可以通过CRM客户管理系统实现高效的客户关系管理&#xff0c;进一步提高市场竞争力。 建立高效易用的客户关系管理系统 …

OpenAI重磅推出GPTs,无需编码人人可以自定ChatGPT!

原创 | 文 BFT机器人 在11月7日深夜2点&#xff08;北京时间&#xff09;&#xff0c;美国旧金山举办了首届开发者大会&#xff0c;该活动由AI领域的知名公司OpenAI主办。尽管这是该公司的首届大会&#xff0c;但其盛大的规模和影响力已将其誉为“AI春晚”。在会议上&#xff…

关于echarts封装组件以及多次更新数据信息加载问题

项目中经常使用到echarts插件&#xff0c;使用时会遇到封装组件的问题&#xff0c;一个组件到底怎么封装才是完善的&#xff1f;仁者见仁智者见智思路不同封装的方式就是不同的。废话不多直接上封装的代码&#xff1a; <template><div :id"id" :style"…

OpenGL_Learn08(坐标系统与3D空间)

目录 1. 概述 2. 局部空间 3. 世界空间 4. 观察空间 5. 剪裁空间 6. 初入3D 7. 3D旋转 8. 多个正方体 9. 观察视角 1. 概述 OpenGL希望在每次顶点着色器运行后&#xff0c;我们可见的所有顶点都为标准化设备坐标(Normalized Device Coordinate, NDC)。也就是说&#x…

两台Linux服务器之间传送文件

两台Linux服务器之间传送文件 将U盘上的数据传送到服务器上 本地U盘 远程服务器地址&#xff1a; 192.168.30.125 传送到data文件夹的下面 scp -r coco2017 s192.168.30.125:/data传送 两台Linux服务器之间传送文件的4种方法

pytorch搭建squeezenet网络的整套工程(升级版)

上一篇当中&#xff0c;使用pytorch搭建了一个squeezenet&#xff0c;效果还行。但是偶然间发现了一个稍微改动的版本&#xff0c;拿来测试一下发现效果会更好&#xff0c;大概网络结构还是没有变&#xff0c;还是如下的第二个版本&#xff1a; 具体看网络结构代码&#xff1a…

进程控制——进程的程序替换

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;那个传说中的man的主页 &#x1f3e0;个人专栏&#xff1a;题目解析 &#x1f30e;推荐文章&#xff1a;题目大解析&#xff08;3&#xff09; 目录 &#x1f449;&#x1f3fb;进程的程序替换概念&#x…

R语言和jsonlite库编写代码示例

R语言和jsonlite库来下载的程序。 r # 导入jsonlite库 library(jsonlite) # 设置代理主机和端口 proxy_host <- "" proxy_port <- # 使用httr库创建一个对象 proxy <- create_proxy(proxy_host, proxy_port) # 使用httr库的GET方法下载网页内容 url <…

输入网址到网页显示,期间发生了什么?(收藏篇)

解析url 首先浏览器做的第一步工作就是要对 URL 进行解析&#xff0c;从而生成发送给 Web 服务器的请求信息。对 URL 进行解析之后&#xff0c;浏览器确定了 Web 服务器和文件名&#xff0c;接下来就是根据这些信息来生成 HTTP 请求消息了。 DNS解析 通过浏览器解析 URL 并…

学生用什么台灯对眼睛最好?双十一优质好用护眼台灯推荐

护眼台灯作为时下火爆的学生清单单品&#xff0c;深受众多学生以及家长的认可&#xff0c;市面上的护眼台灯品牌众多&#xff0c;但很多品牌存在不合格、劣质、虚假宣传的问题&#xff0c;为了帮大家避坑&#xff0c;我这个资深测评师反复测评了三十多款&#xff0c;今天给大家…

【Linux】进程程序替换

文章目录 替换原理站在进程的角度站在程序的角度初体验及理解原理 替换函数函数解释命名理解exec系列函数与main函数之间的关系在一个程序中调用我们自己写的程序 替换原理 创建子进程的目的是什么&#xff1f; ->想让子进程执行父进程代码的一部分 执行父进程对应的磁盘代码…

ROS源码安装应用,VSCode

ROS源码安装应用 安装一下VSCode 前置文章 到安装程序的目录中: 完成克隆 编译 catkin_make打开ros核心 roscore打开应用程序 rosrun turtlesim turtlesim_node安装一下VSCode deb下载地址 sudo dpkg -i code_1.84.1-1699275408_amd64.deb添加项目工程到工作空间&#xff…

什么是伺服电机?Parker派克伺服电机盘点

一、什么是伺服电机&#xff1f; 要准确地定义伺服电机&#xff0c;我们首先需理解其核心特性&#xff1a;反馈与闭环控制。伺服电机凭借这些特性&#xff0c;能精确控制扭矩、速度或位置&#xff0c;即使在零速度下&#xff0c;也能保持足够的扭矩以锁定负载。 伺服电机与其…

JVS-智能BI数据集管理:打造高效、准确数据应用基础

现今的数字化、智能化时代&#xff0c;数据被誉为企业的新型资产&#xff0c;企业面临的数据量日益庞大&#xff0c;如何对这些数据进行高效、准确的管理和应用&#xff0c;直接决定了企业的运营效率和决策水平。 在这样的背景下&#xff0c;仅仅收集数据并不足够&#xff0c;…

IDEA 关闭SpringBoot启动Logo/图标

一、环境 1、SpringBoot 2.6.4 Maven POM格式 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.6.4</version><relativePath/></parent> 2、IDE…

第五章《数据降维:深入理解 PCA 的来龙去脉》笔记

主成分分析(Principal Component Analysis&#xff0c;PCA) 就是机器学习中一种常用且有效的数据降维方法。 5.1 PCA是什么 PCA 将相关性高的变量转变为较少的独立新变量&#xff0c;实现用较少的综合指标分别代表存在于 各个变量中的各类信息&#xff0c;既减少高维数据的变…