微信開放平臺開發第三方授權登陸(三):Android客戶端

微信開放平臺開發系列文章:

微信開放平臺開發第三方授權登陸(一):開發前期準備

微信開放平臺開發第三方授權登陸(二):PC網頁端

微信開放平臺開發第三方授權登陸(三):Android客戶端

微信開放平臺開發第三方授權登陸(四):微信公衆號

微信開放平臺開發第三方授權登陸(五):微信小程序

 

目錄​​​​​​​

一、需求

二、開發流程

三、開發使用的技術及工具

四、具體實現步驟

1.前端(Android)

1)Android微信授權登錄開發環境配置

2)引導用戶點擊登錄並授權

3)接收微信服務端返回的數據並向服務端發送請求

4)根據服務端返回數據進行解析並顯示給前端Android頁面

2.服務端(Java)

1).統一返回JSON

2).相關參數配置:

3).請求響應邏輯

4).根據Token獲取用戶信息:

五、注意事項:

六、應用關鍵參數位置


微信開放平臺開發第三方授權登陸(一):開發前期準備完成後,已經獲取到應用的AppID和AppSecret、且已經成功申請到微信登陸功能。可以進行第三方登陸授權開發。

注意:

目前移動應用上微信登錄只提供原生的登錄方式,需要用戶安裝微信客戶端才能配合使用

對於Android應用,建議總是顯示微信登錄按鈕,當用戶手機沒有安裝微信客戶端時,請引導用戶下載安裝微信客戶端

 

開放平臺中創建移動應用時,需要添加包名(一定要和開發的包名完全一致,不能是填寫的包名的子包,否則微信無法回調成功)

安裝驗籤工具:Gen_Signature_Android2.apk

填寫包名,然後會生成應用簽名,填寫應用簽名就可以了。

 

一、需求

擁有第三方微信登錄功能,並獲取到用戶信息。

二、開發流程

Android移動應用:(App喚醒微信客戶端授權登陸)

1. 應用發起微信授權登錄請求,用戶允許授權應用後,微信會拉起應用或重定向到第三方網站(服務端),並且帶上授權臨時票據code參數;

2. 通過code參數加上AppID和AppSecret等,通過API換取access_token;

3. 通過access_token進行接口調用,獲取用戶基本數據資源或幫助用戶實現基本操作。

獲取用戶基本信息的流程

 

三、開發使用的技術及工具

1、.後端採用IDEA2017 進行開發

2、使用Android Studio 3.1.3 進行開發

3、後端必須基於JDK7以上版本,採用JDK8開發,前端基於Android SDK4.4

4、使用fastJson對json數據進行處理

四、具體實現步驟

1.前端(Android)

目錄結構如下:

1)Android微信授權登錄開發環境配置

I.添加微信依賴

Android Studio環境

在build.gradle文件中,添加依賴

dependencies {

    compile 'com.tencent.mm.opensdk:wechat-sdk-android-with-mta:+'

}

dependencies {

    compile 'com.tencent.mm.opensdk:wechat-sdk-android-without-mta:+'

}

Eclipse環境下:

在工程中新建一個libs目錄,將開發工具包中libs目錄下的libammsdk.jar複製到該目錄中(如下圖所示,建立了一個名爲SDK_Sample 的工程,並把jar包複製到libs目錄下)。

https://res.wx.qq.com/op_res/yXeM-qkPNMo3NZ6AOSZ0x8MqkBf9ATOfaw-2Ic93vUG8xFid8niGKr3W_RfCmMxe

右鍵單擊工程,選擇Build Path中的Configure Build Path...,選中Libraries這個tab,並通過Add Jars...導入工程libs目錄下的libammsdk.jar文件。(如下圖所示)。

https://img-blog.csdnimg.cn/20181225013003776

在需要使用微信終端API的文件中導入相應的類。

import com.tencent.mm.opensdk.openapi.WXTextObject;

 

 

II. AndroidManifest.xml 設置

添加如下權限支持:

    <!--權限聲明-->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

III.若要混淆代碼,爲保證sdk正常使用,需在配置proguard.cfg(proguard-rule.pro):

# wechat
-keep class com.tencent.mm.opensdk.** {*;}
-keep class com.tencent.wxop.** {*;}
-keep class com.tencent.mm.sdk.** {*;}

2)引導用戶點擊登錄並授權

I.layout.xml

添加button:

        <Button
            android:id="@+id/wechat_login_btn"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:text="@string/wechat_login"/>

II.監聽button點擊事件,拉起微信授權頁

       findViewById(R.id.wechat_login_btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (isWXAppInstalledAndSupported()) {  // 用戶是否安裝微信客戶端
                    // send oauth request
                    final SendAuth.Req req = new SendAuth.Req();
                    req.scope = "snsapi_userinfo";
                    req.state = "none";
                    api.sendReq(req);
                    finish();
                } else {
                    // TODO: 這裏需要引導用戶去下載微信客戶端
                    Toast.makeText(WXEntryActivity.this, "用戶沒有安裝微信", Toast.LENGTH_SHORT).show();
                }
            }
        });

III.用戶手機是否安裝微信客戶端檢查  

  private boolean isWXAppInstalledAndSupported() {
        IWXAPI msgApi = WXAPIFactory.createWXAPI(this, null);
        msgApi.registerApp(Constants.APP_ID);
        boolean sIsWXAppInstalledAndSupported = msgApi.isWXAppInstalled()
                && msgApi.isWXAppSupportAPI();
        return sIsWXAppInstalledAndSupported;
    }

3)接收微信服務端返回的數據並向服務端發送請求

用戶統一授權後,微信會返回數據,需要在.wxapi.WXEntryActivity下對數據進行處理。

I.新建wxapi包(包名固定,且必須是在微信開放平臺註冊的包名下)

II.新建Activity類,命名爲WXEntryActivity

WXEntryActivity,並繼承Activity類,實現IWXAPIEventHandler接口的兩個方法

public interface IWXAPIEventHandler {
    void onReq(BaseReq var1);
    void onResp(BaseResp var1);
}

WXEntryActivity實現

public class WXEntryActivity extends Activity implements IWXAPIEventHandler {

private IWXAPI api;  // 在onCreate中進行了初始化

onReq方法

    // 微信發送請求到第三方應用時,會回調到該方法
    @Override
    public void onReq(BaseReq req) {
        Toast.makeText(this, "Test ", Toast.LENGTH_SHORT).show();
        switch (req.getType()) {
            case ConstantsAPI.COMMAND_GETMESSAGE_FROM_WX:
                break;
            case ConstantsAPI.COMMAND_SHOWMESSAGE_FROM_WX:
                break;
            default:
                break;
        }
}

onResp方法

在onResp中需要實現邏輯,微信返回的數據在這裏會被接收。

微信返回的數據包含code。在onResp需要實現向服務端發送請求,帶上code等參數,後端再通過相應的參數去請求微信服務端,最終將獲取到的用戶信息返回給前端Android。

// 第三方應用發送到微信的請求處理後的響應結果,會回調到該方法
    @Override
    public void onResp(final BaseResp resp) {
        int result = 0;

        Toast.makeText(this, "baseresp.getType = " + resp.getType(), Toast.LENGTH_SHORT).show();
        //成功後發送請求
        switch (resp.errCode) {
            case BaseResp.ErrCode.ERR_OK:
                result = R.string.errcode_success;
                final String code = ((SendAuth.Resp) resp).code;//需要轉換一下才可以
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                //向服務端發送請求,預計返回用戶信息數據,返回給前端進行顯示。
                        String url = "http://p2a3b8.natappfree.cc" +
                                "/wechat/open/callback/android" + "?" +
                                "state=" + "android" +//這裏的state需與後端進行探討
                                "&code=" + code;
                        String str = ApacheHttpUtil.get(url);
                        JSONObject jsonObject = (JSONObject) JSONObject.parse(str);
                        weChatUserInfo = (WeChatUserInfo) JSON.parseObject(jsonObject.get("data").toString(), new TypeReference<WeChatUserInfo>() {
                        });
                    }
                }).start();
                while (true) {
// TODO: 這裏處理方案不合理,死循環或將造成界面卡死(需要前端優化)
                    if (weChatUserInfo != null) {
                        Intent intent = new Intent(WXEntryActivity.this, WechatUserInfoViewItem.class);
                        /* 通過Bundle對象存儲需要傳遞的數據 */
                        Bundle bundle = new Bundle();
                        bundle.putString("wechatopenid", weChatUserInfo.getOpenid());
                        bundle.putString("wechatnickname", weChatUserInfo.getNickname());
                        bundle.putString("wechatsex", weChatUserInfo.getSex().toString());
                        bundle.putString("wechatprovince", weChatUserInfo.getProvince());
                        bundle.putString("wechatcity", weChatUserInfo.getCity());
                        bundle.putString("wechatcountry", weChatUserInfo.getCountry());
                        bundle.putString("wechatheadimgurl", weChatUserInfo.getHeadimgurl());
                        bundle.putString("wechatprivilege", weChatUserInfo.getPrivilege());
                        bundle.putString("wechatunionid", weChatUserInfo.getOpenid());
                        /*把bundle對象assign給Intent*/
                        intent.putExtras(bundle);
                        startActivity(intent);
                        break;
                    }
                }
                break;
            case BaseResp.ErrCode.ERR_USER_CANCEL:
                result = R.string.errcode_cancel;   // 發送取消
                break;
            case BaseResp.ErrCode.ERR_AUTH_DENIED:
                result = R.string.errcode_deny;   // 發送被拒絕
                break;
            case BaseResp.ErrCode.ERR_UNSUPPORT:
                result = R.string.errcode_unsupported;  // 不支持錯誤
                break;
            default:
                result = R.string.errcode_unknown;  // 發送返回
                break;
        }
        Toast.makeText(this, result, Toast.LENGTH_LONG).show();
    }

重寫onNewIntent方法

在WXEntryActivity中將接收到的intent及實現了IWXAPIEventHandler接口的對象傳遞給IWXAPI接口的handleIntent方法,

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        setIntent(intent);
        api.handleIntent(intent, this);
    }

III.在manifest文件添加WXEntryActivity,並加上exported屬性,設置爲true,:

        <activity
            android:name=".wxapi.WXEntryActivity"
            android:exported="true"
            android:label="@string/app_name"
            android:launchMode="singleTask">
            <!--android:launchMode="singleTop">-->
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:scheme="sdksample" />
            </intent-filter>
        </activity>

4)根據服務端返回數據進行解析並顯示給前端Android頁面

獲取數據已經跳轉代碼如上(onResp方法中)。需要前端優化處理

public class WechatUserInfoViewItem extends FragmentActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.wechat_user_info);
        Bundle bundle = this.getIntent().getExtras();
        TextView wechatopenid = (TextView) findViewById(R.id.wechatopenid);
        wechatopenid.setText(bundle.getString("wechatopenid"));
        TextView wechatnickname = (TextView) findViewById(R.id.wechatnickname);
        wechatnickname.setText(bundle.getString("wechatnickname"));
        TextView wechatsex = (TextView) findViewById(R.id.wechatsex);
        wechatsex.setText(bundle.getString("wechatsex"));
        TextView wechatprovince = (TextView) findViewById(R.id.wechatprovince);
        wechatprovince.setText(bundle.getString("wechatprovince"));
        TextView wechatcity = (TextView) findViewById(R.id.wechatcity);
        wechatcity.setText(bundle.getString("wechatcity"));
        TextView wechatcountry = (TextView) findViewById(R.id.wechatcountry);
        wechatcountry.setText(bundle.getString("wechatcountry"));
        TextView wechatunionid = (TextView) findViewById(R.id.wechatunionid);
        wechatunionid.setText(bundle.getString("wechatunionid"));
    }
}

佈局xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical">
    <ImageView
        android:id="@+id/wechatheadimgurl"
        android:layout_width="200dp"
        android:layout_height="100dp"
        android:scaleType="center"/>
   <!--用戶特權信息,json 數組,如微信沃卡用戶爲(chinaunicom)[這裏是一個list]-->
    <!--String privilege;-->
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="openid:"
            tools:layout_editor_absoluteX="158dp"
            tools:layout_editor_absoluteY="216dp" />
        <TextView
            android:id="@+id/wechatopenid"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            tools:layout_editor_absoluteX="158dp"
            tools:layout_editor_absoluteY="216dp" />
    </LinearLayout>
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="unionid:"
            tools:layout_editor_absoluteX="158dp"
            tools:layout_editor_absoluteY="216dp" />
        <TextView
            android:id="@+id/wechatunionid"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            tools:layout_editor_absoluteX="158dp"
            tools:layout_editor_absoluteY="216dp" />
    </LinearLayout>
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="用戶暱稱:"
            tools:layout_editor_absoluteX="158dp"
            tools:layout_editor_absoluteY="216dp" />
        <TextView
            android:id="@+id/wechatnickname"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            tools:layout_editor_absoluteX="85dp"
            tools:layout_editor_absoluteY="212dp" />
    </LinearLayout>
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="用戶城市:"
            tools:layout_editor_absoluteX="158dp"
            tools:layout_editor_absoluteY="216dp" />
        <TextView
            android:id="@+id/wechatcity"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            tools:layout_editor_absoluteX="118dp"
            tools:layout_editor_absoluteY="302dp" />
    </LinearLayout>
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="用戶性別:"
            tools:layout_editor_absoluteX="158dp"
            tools:layout_editor_absoluteY="216dp" />
        <TextView
            android:id="@+id/wechatsex"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            tools:layout_editor_absoluteX="118dp"
            tools:layout_editor_absoluteY="302dp" />
    </LinearLayout>
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="省份:"
            tools:layout_editor_absoluteX="158dp"
            tools:layout_editor_absoluteY="216dp" />
        <TextView
            android:id="@+id/wechatprovince"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            tools:layout_editor_absoluteX="118dp"
            tools:layout_editor_absoluteY="302dp" />
    </LinearLayout>
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="國家:"
            tools:layout_editor_absoluteX="158dp"
            tools:layout_editor_absoluteY="216dp" />
        <TextView
            android:id="@+id/wechatcountry"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            tools:layout_editor_absoluteX="118dp"
            tools:layout_editor_absoluteY="302dp" />
    </LinearLayout>
</LinearLayout>

2.服務端(Java)

服務端需要做的是:接受Android前端發送的請求,獲取code,根據AppId和APPSecret等向微信服務端發送請求,然後獲取到Token,再根據Token獲取到用戶基本信息。最終通過JSON的方式返回給前端。

1).統一返回JSON

@Getter @Setter
public class Result<T> {
    private int code;
    private String msg;
    private T data;

    /*** 成功時候的調用* */
    public static <T> Result<T> success(T data){
        return new  Result<T>(data);
    }

    /*** 失敗時候的調用* */
    public static <T> Result<T> error(CodeMsg cm){
        return new  Result<T>(cm);
    }

    private Result(T data) {
        this.code = 0;
        this.msg = "success";
        this.data = data;
    }

    private Result(CodeMsg cm) {
        if(cm == null) {
            return;
        }
        this.code = cm.getCode();
        this.msg = cm.getMsg();
    }
}

 

2).相關參數配置:

# 微信開放平臺Android

wechat.open.android.appid =

wechat.open.android.appsecret =

 

3).請求響應邏輯

@ResponseBody
    @RequestMapping("/callback/android")
    public Result openWeChatCallback(HttpServletRequest httpServletRequest) {
        String code = httpServletRequest.getParameter("code");
        //String state = httpServletRequest.getParameter("state"); // TODO:
        String url = null;
        url = "https://api.weixin.qq.com/sns/oauth2/access_token?" +
                "appid=" +
                env.getProperty("wechat.open.android.appid").trim() +
                "&secret=" +
                env.getProperty("wechat.open.android.appsecret").trim() +
                "&code=" +
                code +
                "&grant_type=authorization_code";
        JSONObject wechatAccessToken = HttpClientUtils.httpGet(url);
        if (wechatAccessToken.get("errcode") != null) {
            return Result.error(CodeMsg.FAIL_GETTOKEN);
        }
        String accessToken = (String) wechatAccessToken.get("access_token");
        String openid = (String) wechatAccessToken.get("openid");
        String unionid = (String) wechatAccessToken.get("unionid");

        if (StringUtils.isEmpty(accessToken) || StringUtils.isEmpty(openid) || StringUtils.isEmpty(unionid)) {
            return Result.error(CodeMsg.FAIL_GETTOKEN);
        }
        // TODO:根據Openid或Unionid對數據庫進行查詢,如果查詢到對應的用戶數據,則不需要再向微信服務器發送請求去返回數據。
        // TODO: 建議使用Unionid作爲查詢條件。
        WeChatUserInfo weChatUserInfo = null;
        wechatAccessToken = null;  // FIXME: 這裏應該是從數據庫中查詢獲取用戶信息邏輯。
        if (wechatAccessToken == null) {
            // 新用戶
            weChatUserInfo = getUserInfoByAccessToken(accessToken);
            // 數據庫插入的操作
        }
        if (weChatUserInfo != null) {
            return Result.success(weChatUserInfo);
        }
        return Result.error(CodeMsg.FAIL_GETUSERINFO);
    }

4).根據Token獲取用戶信息:

/**
     * 根據accessToken獲取用戶個人公開信息
     *
     * @param accessToken
     * @return
     */
  private WeChatUserInfo getUserInfoByAccessToken(String accessToken) {
        if (StringUtils.isEmpty(accessToken)) {
            return null;  //"accessToken爲空";
        }
        String get_userInfo_url = "https://api.weixin.qq.com/sns/userinfo?" +
                "access_token=" +
                accessToken +
                "&openid=" +
                env.getProperty("wechat.open.android.appid").trim();
        String userInfo_result = HttpClientUtils.httpGet(get_userInfo_url, "utf-8");
        if (!userInfo_result.equals("errcode")) {
            WeChatUserInfo weChatUserInfo = JSON.parseObject(userInfo_result, new TypeReference<WeChatUserInfo>() {
            });
            // TODO: 需要把頭像信息下載到文件服務器,然後替換掉頭像URL。微信的或許不可靠,假設微信用戶更換了頭像,舊頭像URL是否會保存?而這個URL信息卻存放在我們的數據庫中,不可靠
            return weChatUserInfo;
        }
        return null;  //"獲取用戶信息失敗"
    }

五、注意事項:

1.Android4.0以上版本,發送網絡請求時,必須是以線程異步的方式發送請求,否則發送請求會失敗。

六、應用關鍵參數位置

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章