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

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

背景

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

主要步骤

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

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

1.2 在项目下创建应用,根据实际情况可选,安卓、ios、web类型。【外部服务对接】对接Firebase支持谷歌、Facebook、苹果等第三方平台用户注册登录_第2张图片
1.3 生成自己项目的私钥文件,下载保存到java应用中

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

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.里面还有应用外消息推送的代码,可以针对用户手机推送业务消息

你可能感兴趣的:(Firebase,注册登录,消息tuisong,消息推送,java,后端)