很早前就想了解下微信公众号开发,懒和拖延症让这计划迟迟没落实,正巧新项目有了微信公众号的业务,我心中没数地回应,我来!顿时,无形的压力向上冒,不知何从下手,果然是矛盾体质,好奇向往新事物又恐惧陌生,心中两种声音在争论,保守派说你摊上了,激进派说微信小程序都推行了,还不去了解下公众号开发想当奥特蜗曼呀。是啊,做技术的,学习和创新就是本质工作之一啊。减少社交活动,暂时戒掉部分兴趣爱好...与电脑并肩作战了两个月,时间是长还是短没概念,只知道走了不少弯路也收获了很多,为避免走同样的弯路,把脑子里的零碎整理成了笔记。
在开始coding前做了很多微信相关的功课,回头想想,其实公众号开发和微信只有几毛钱关系,主要的工作量还是在于本身系统的业务逻辑,看的白一点就是把有一定组织架构的h5页面放web服务器,通过微信提供的各种接口调用微信公众号所具备的功能来服务你的系统。微信只是你系统的入口或者客户端,你所需要的技能也是java web那一套。
我的项目采用这样的技术,
后台:spring + springMVC + ibetis; 原本使用的是最常规架构controller,service,dao,pojo的结构,但为了以后的维护,改成了和主系统一样的架构 controller , model , business command和database command,dao ,pojo,其中controller和平常所说的controller一样主要负责与页面的逻辑,model相当于serice, 继承了pojo, 调用business command, business command对database command进行调用
前端:html5,css3,bootstrap,jquery;
DB:Mysql;
第三方接口:微信公众号网页授权接口, 微信公众号网页支付接口, 支付宝手机网站支付接口;阿里大于短信接口, 快递100物流接口;
开发工具: idea ,eclipse, sublim, navicat, dbvisualizer, beyond compare,Filezilla, Git,notepad++ ,微信web开发者工具,花生壳, chrome, firefox,还有网易云音乐;
测试环境: 阿里云服务器(linux)+jdk+tomcat8.0+mysql;
实用网址: 图标素材 http://www.iconfont.cn
bootstrap 说明书 http://v3.bootcss.com/components
微信公众平台 https://mp.weixin.qq.com/cgi-bin/loginpage?t=wxm2-login&lang=zh_CN
微信公众平台wiki https://mp.weixin.qq.com/wiki
微信开放平台 https://open.weixin.qq.com/
微信商户平台 https://pay.weixin.qq.com/index.php/core/home /login?return_url=%2Findex.php%2Fcore%2Fhome
微信服务商平台 https://pay.weixin.qq.com/index.php/partner/public/home
微信公众平台支付接口调试工具 https://mp.weixin.qq.com/debug/cgi-bin/readtmpl?t=pay/index
微信公众号接口测试 http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login
支付宝商家 https://b.alipay.com/order/signManage.htm
支付宝开放平台https://www.ant-open.com/platform/home.htm
相因的服务器新浪sae http://www.sinacloud.com/?from=baidu_web
阿里大于 http://www.alidayu.com/
快递100 http://www.kuaidi100.com/
以上网址会在后面的文章中陆续出现。关于接口的调用腾讯有长篇大论的文档,显得很复杂,再看白一点就是将接口需要的参数拼接成接口所要求的格式发送http请求,在发送之前对你的参数进行MD5加密,生成所需要的签名后,再将签名和参数一起向目标地址发送请求,原理满简单吼,但在处理细节时不小心就跳进坑里,所以官方提供的文档一定要看。
切入正题,网页授权开摆...
一、 申请微信公众号
申请地址:https://mp.weixin.qq.com/cgi-bin/registermidpage?action=index&lang=zh_CN
根据自己的需求选择适合的帐号类型
所在项目类似微商城,所以选择了服务号,在申请的时候需要上传公司营业执照等公司信息,个人只能申请普通订阅号,这种帐号类型没有高级接口功能,比如读取用户资料,支付等等等,所以仅仅想学习或者测试接口或以申请测试帐号(http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login)。
二、申请开放平台
公众号申请成功后,coding的事别急,先找接口文档,俗话说不耐心看文档的程序员不是好测试。登录公众平台->接口权限->网页服务->网页授权->网页授权获取用户基本信息(https://mp.weixin.qq.com/wiki)
为什么需要获取用户信息了?
当用户通过微信进入指定系统时(我们这里就叫我们的系统为系统A),系统A需要给访问者唯一标识,以前的系统都是让访问者注册,这里既然客户端是微信那么我们就使用微信的第三方认证方式,但微信出于对用户信息的保护就将这个唯一identity交给了openId和 unionId 在我们项目中openId/unionId的用途如下:
什么是openId?
openId是针对一个公众号开发,用户在这公众号中是唯一的,在不同的公众号中用户的openId不同;
什么是unionId?
unionId是针对同一个公司下多个公众号之间需要用户帐号互通的一种机制;即同一用户在同一开放平台下的公众号是唯一的,当然在不同开放平台下就不同了。
开放平台又是什么?
图片所示
开放平台申请地址(http://open.weixin.qq.com/),过程略,要收费哟,300块。
我们项目的场景是这样的,用户在微信端操作的数据需要传递到app端,刚才的图片已经告诉我们,app和微信间的交互不是通过公众平台,而是开放平台,这时要获取unionId,刚才准备好的公众号就不够用了,就还需要将已申请的公众平台绑定到申请好的开放平台上;
绑定公众号到开放平台
点击顶部 “管理中心”,选择进入“公众号”,再点击“绑定公众号”
三、获取openId和unionId
网页授权类型:snsapi_base和snsapi_userinfo
snsapi_base:静默授权,用户无感知。(需要用户关众该公众号才能获取openId? 网上各种版本都有,腾讯也没说清楚,经过测试不需要)
snsapi_userinfo:需要用户手动同意,由于用户同意过,所以无须关注,就可在授权后获取该用户的基本信息,对于已关注公众号的用户,如果用户从公众号的会话或者自定义菜单进入本公众号的网页授权页,即使是scope为snsapi_userinfo,也是静默授权,用户无感知。提示用户是否同意授权的窗口如下图:
网页授权回调
微信授权成功之后,接下来就是进入你所设置的回调地址,他的用途是授权成功后页面的跳转,在这个地址这个地址中session是有效的,可以在这个地址中用request取code的值。该地址需要到公众平台先进行设置 :
开发 - 接口权限 - 网页服务 - 网页帐号 - 网页授权获取用户基本信息
点击修改,填写回调地址所在域名,本想勾出重点,但发现每条都是重点。
如果没有域名可以申请一个sina云(http://www.sinacloud.com/?from=baidu_web),将你的系统部署上去,最低配置的免费。
回调域名除了图片上的要求还需要注意:
1.该域名(是一个字符串),而不是URL,因此请勿加 http:// 等协议头;
2、授权回调域名配置规范为全域名,比如需要网页授权的域名为:www.qq.com,配置以后此域名下面的页面http://www.qq.com/music.html 、 http://www.qq.com/login.html 都可以进行OAuth2.0鉴权。但http://pay.qq.com 、 http://music.qq.com 、 http://qq.com无法进行OAuth2.0鉴权
3、如果公众号登录授权给了第三方开发者来进行管理,则不必做任何设置,由第三方代替公众号实现网页授权即可
授权步骤
网页授权流程分为四步,流程在文档中写的很清楚,我就将每步代码化吧
1、引导用户进入授权页面同意授权,获取code
常量定义如下:
AppID = XXX
APPSECRET = XXX
REDIRECT_URI = http://XXX/common/wechat_auth.html
GET_CODE_URL = https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
GET_OPENID_URL = https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=APPSECRET&code=CODE&grant_type=authorization_code
GET_ACCESSTOKEN_URL = https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
GET_UNIONID_URL = https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID
SCOPE = snsapi_base
在你的action中生成访问的url
String getCodeUrl = Constants.GET_CODE_URL;
getCodeUrl = getCodeUrl.replace("APPID", Constants.APPID);
getCodeUrl = getCodeUrl.replace("REDIRECT_URI", java.net.URLEncoder.encode(Constants.REDIRECT_URI,"utf-8"));
getCodeUrl = getCodeUrl.replace("SCOPE", Constants.SCOPE);
getCodeUrl = getCodeUrl.replace("STATE", entanceType);
getCodeUrl 成功生成
2、通过code换取网页授权access_token(与基础支持中的access_token不同)
这时能过request到你设置 好的回调地址中取拿到的code.
@RequestMapping("wechat_auth.html")
public ModelAndView wechatOauth2( HttpServletRequest request) {
String code = request.getParameter("code");code到手
String state = request.getParameter("state");
这个state是干嘛的呢,用于传递自定义参数,回过去看在获取code的地址中有传递一个state吧,在返回code的时候state将原样返回。
3、如果需要,开发者可以刷新网页授权access_token,避免过期
4、通过网页授权access_token和openid获取用户基本信息(支持UnionID机制)
/**
* 根据code获取openId和unionId
* @param code
* @return 返回openId和unionId
*/
public static String[] getOpenId(String code ) {
String args[] = new String[2];
//获取openid
String oauth2URL = Constants.GET_OPENID_URL;
oauth2URL = oauth2URL.replace("APPID", Constants.APPID);
oauth2URL = oauth2URL.replace("APPSECRET", Constants.APPSECRET);
oauth2URL = oauth2URL.replace("CODE", code);
System.out.println("oauth2URL::" + oauth2URL);
String authResponseJsonStr = HttpUtils.getUrl(oauth2URL);
System.out.println("authResponseJsonStr::" + authResponseJsonStr);
JSONObject responseJsonObject = JSONObject.parseObject(authResponseJsonStr);
args[0] = responseJsonObject.getString("openid");
//获取token
String tokenUrl2 =Constants.GET_ACCESSTOKEN_URL;
tokenUrl2 = tokenUrl2.replace("APPID", Constants.APPID);
tokenUrl2 = tokenUrl2.replace("APPSECRET", Constants.APPSECRET);
String tokenResponseJsonStr2 = HttpUtils.getUrl(tokenUrl2);
System.out.println("tokenResponseJsonStr2::" + tokenResponseJsonStr2);
JSONObject tokenResponseJsonObject2 = JSONObject.parseObject(tokenResponseJsonStr2);
String accessToken = tokenResponseJsonObject2.getString("access_token");
//根据openid和token获取unionid
String unionidUrl = Constants.GET_UNIONID_URL;
unionidUrl = unionidUrl.replace("ACCESS_TOKEN",accessToken);
unionidUrl = unionidUrl.replace("OPENID",args[0]);
String unionidResponseJsonStr = HttpUtils.getUrl(unionidUrl);
System.out.println("unionidResponseJsonStr::" + unionidResponseJsonStr);
JSONObject unionidresponseJsonObject = JSONObject.parseObject(unionidResponseJsonStr);
args[1] = unionidresponseJsonObject.getString("unionid");
return args;
}
把得到的数组system.out.print出来,可以去喝点水了。
成功返回的json字符串长这样:
{ "openid":" OPENID",
" nickname": NICKNAME,
"sex":"1",
"province":"PROVINCE"
"city":"CITY",
"country":"COUNTRY",
"headimgurl": "http://wx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ
4eMsv84eavHiaiceqxibJxCfHe/46",
"privilege":[ "PRIVILEGE1" "PRIVILEGE2" ],
"unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}
这是snsapi_userinfo类型的返回,如果是snsapi_base就没有nickname等个人信息字段。
四 、关于测试
你在测试微信授权时会发现在web浏览器里会被提示"请用微信客户端打开",不管是在手机微信还是电脑端微信测试了又会提示 "网站没有备案"之类的提示,localhost和ip地址更是不可行。这时微信web开发者工具就刚好解决这些难题,不仅可以测试功能,还可以debug, 有着和chrome开发者工具栏类似的功能。当然,如果你的系统授权逻辑还没写好,只有一串获取code的Url,想测试配置是否正确,比如:https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx520c15f417810387&redirect_uri=https%3A%2F%2Fchong.qq.com%2Fphp%2Findex.php%3Fd%3D%26c%3DwxAdapter%26m%3DmobileDeal%26showwxpaytitle%3D1%26vb2ctag%3D4_2030_5_1194_60&response_type=code&scope=snsapi_base&state=123#wechat_redirect。直接贴到web开发者工具地址栏,再到你的回调地址中用request.getParameter("code")取得code. 这里的回调地址前面已经提到需要是域名,而且只能是80端口,但你又想在本地调试,这里需要借力于花生壳将本地服务映射成对外访问的域名,再将该域名绑定到你申请好且备案的域名上。
我们在开发过程中经常会考虑自己的成果是否人性化,包括文档和代码的可读性,功能的易操作性...经过调用了很多第三方接口后有了新的认识,产品的人性化固然重要,产品的认可度更重要。看人家的文档和demo再想吐也会吐着看完,反正你非得调用我的接口,不清楚你也会研究,总有一天你会调用成功。当你调用成功后才恍然大悟,晕啊,原来那句话是那意思,说好的人性化呢。