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


    【外部服务对接】对接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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    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='用户授权信息表';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    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
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    注意

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

  • 相关阅读:
    如何在PostgreSQL数据库中插入当前时间
    【操作系统】操作系统笔记
    109.(leaflet篇)leaflet实现沿轨迹线播放
    5年经验的大厂测试/开发程序员,怎样突破技术瓶颈?大厂通病......
    Elasticsearch 应用
    Llama 2 论文《Llama 2: Open Foundation and Fine-Tuned Chat Models》阅读笔记
    【libcurl】7.84.0 : windows上的cmake构建x64 release版本
    fatfs对于exFAT的使用
    学编程:Python入门考级必备[8]
    利用Quartz设计采集系统并实现系统双活机制_在SpringCloud中自己设计系统双活---SpringCloud工作笔记178
  • 原文地址:https://blog.csdn.net/sucess_zhang/article/details/134326022