微信开放平台开发第三方授权登陆(一):开发前期准备
微信开放平台开发第三方授权登陆(二):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数据进行处理
目录结构如下:
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目录下)。
右键单击工程,选择Build Path中的Configure Build Path...,选中Libraries这个tab,并通过Add Jars...导入工程libs目录下的libammsdk.jar文件。(如下图所示)。
在需要使用微信终端API的文件中导入相应的类。
import com.tencent.mm.opensdk.openapi.WXTextObject;
II. AndroidManifest.xml 设置
添加如下权限支持:
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.** {*;}
I.layout.xml
添加button:
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;
}
用户统一授权后,微信会返回数据,需要在.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() {
});
}
}).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,:
获取数据已经跳转代码如上(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:
服务端需要做的是:接受Android前端发送的请求,获取code,根据AppId和APPSecret等向微信服务端发送请求,然后获取到Token,再根据Token获取到用户基本信息。最终通过JSON的方式返回给前端。
@Getter @Setter
public class Result {
private int code;
private String msg;
private T data;
/*** 成功时候的调用* */
public static Result success(T data){
return new Result(data);
}
/*** 失败时候的调用* */
public static Result error(CodeMsg cm){
return new Result(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();
}
}
# 微信开放平台Android
wechat.open.android.appid =
wechat.open.android.appsecret =
@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);
}
/**
* 根据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() {
});
// TODO: 需要把头像信息下载到文件服务器,然后替换掉头像URL。微信的或许不可靠,假设微信用户更换了头像,旧头像URL是否会保存?而这个URL信息却存放在我们的数据库中,不可靠
return weChatUserInfo;
}
return null; //"获取用户信息失败"
}
1.Android4.0以上版本,发送网络请求时,必须是以线程异步的方式发送请求,否则发送请求会失败。