001/** 002 * Copyright (c) 2015-2022, Michael Yang 杨福海 (fuhai999@gmail.com). 003 * <p> 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * <p> 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * <p> 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package io.jboot.wechat.controller; 017 018import com.jfinal.aop.Before; 019import com.jfinal.aop.Clear; 020import com.jfinal.core.NotAction; 021import com.jfinal.kit.HashKit; 022import com.jfinal.weixin.sdk.api.ApiConfig; 023import com.jfinal.weixin.sdk.api.ApiConfigKit; 024import com.jfinal.weixin.sdk.api.ApiResult; 025import com.jfinal.weixin.sdk.api.JsTicket; 026import io.jboot.Jboot; 027import io.jboot.utils.RequestUtil; 028import io.jboot.utils.StrUtil; 029import io.jboot.web.controller.JbootController; 030import io.jboot.wechat.JbootWechatConfig; 031import io.jboot.wechat.WechatApis; 032import io.jboot.wechat.interceptor.WechatApiConfigInterceptor; 033import io.jboot.wechat.interceptor.WechatUserInterceptor; 034 035import javax.servlet.http.HttpServletRequest; 036import java.util.Map; 037import java.util.TreeMap; 038 039@Before({WechatApiConfigInterceptor.class, WechatUserInterceptor.class}) 040public abstract class JbootWechatController extends JbootController { 041 042 public static final String SESSION_WECHAT_OPEN_ID = "_jboot_wechat_open_id_"; 043 public static final String SESSION_WECHAT_ACCESS_TOKEN = "_jboot_wechat_access_token_"; 044 public static final String SESSION_WECHAT_SCOPE = "_jboot_wechat_scope_"; 045 public static final String SESSION_WECHAT_USER_JSON = "_jboot_wechat_json_"; 046 public static final String ATTR_USER_OBJECT = "_jboot_user_object_"; 047 048 049 public ApiConfig getApiConfig() { 050 return ApiConfigKit.getApiConfig(); 051 } 052 053 054 @Clear(WechatUserInterceptor.class) 055 public void wechatCallback() { 056 057 String gotoUrl = getPara("goto"); 058 String code = getPara("code"); 059 060 //获得不到code? 061 if (StrUtil.isBlank(code)) { 062 renderText("获取不到正确的code信息"); 063 return; 064 } 065 066 067 /** 068 * 在某些情况下,相同的callback会执行两次,code相同。 069 */ 070 String wechatOpenId = getSessionAttr(SESSION_WECHAT_OPEN_ID); 071 String accessToken = getSessionAttr(SESSION_WECHAT_ACCESS_TOKEN); 072 073 if (StrUtil.isNotBlank(wechatOpenId) 074 && StrUtil.isNotBlank(accessToken)) { 075 doRedirect(gotoUrl, wechatOpenId, accessToken); 076 return; 077 } 078 079 080 ApiResult result = WechatApis.getAccessTokenAndOpenId(code); 081 if (result == null) { 082 renderText("网络错误,获取不到微信信息,请联系管理员"); 083 return; 084 } 085 086 /** 087 * 成功获取到 accesstoken 和 openid 088 */ 089 if (result.isSucceed()) { 090 wechatOpenId = result.getStr("openid"); 091 accessToken = result.getStr("access_token"); 092 setSessionAttr(SESSION_WECHAT_OPEN_ID, wechatOpenId); 093 setSessionAttr(SESSION_WECHAT_ACCESS_TOKEN, accessToken); 094 setSessionAttr(SESSION_WECHAT_SCOPE, result.getStr("scope")); 095 } else { 096 wechatOpenId = getSessionAttr(SESSION_WECHAT_OPEN_ID); 097 accessToken = getSessionAttr(SESSION_WECHAT_ACCESS_TOKEN); 098 099 if (StrUtil.isBlank(wechatOpenId) || StrUtil.isBlank(accessToken)) { 100 renderText("错误:" + result.getErrorMsg()); 101 return; 102 } 103 } 104 105 if ("snsapi_base".equalsIgnoreCase(result.getStr("scope"))) { 106 redirect(gotoUrl); 107 return; 108 } 109 110 doRedirect(gotoUrl, wechatOpenId, accessToken); 111 } 112 113 private void doRedirect(String gotoUrl, String wechatOpenId, String accessToken) { 114 115 /** 116 * 由于 wechatOpenId 或者 accessToken 是可能从session读取的, 117 * 从而导致失效等问题 118 */ 119 ApiResult apiResult = WechatApis.getUserInfo(accessToken, wechatOpenId); 120 121 if (!apiResult.isSucceed()) { 122 redirect(gotoUrl); 123 return; 124 } 125 126 setSessionAttr(SESSION_WECHAT_USER_JSON, apiResult.getJson()); 127 redirect(gotoUrl); 128 } 129 130 @NotAction 131 public void clearWechatSession() { 132 //移除脏数据后,再次进入授权页面 133 removeSessionAttr(SESSION_WECHAT_OPEN_ID); 134 removeSessionAttr(SESSION_WECHAT_ACCESS_TOKEN); 135 removeSessionAttr(SESSION_WECHAT_USER_JSON); 136 } 137 138 139 @NotAction 140 public void initJsSdkConfig() { 141 142 JbootWechatConfig config = Jboot.config(JbootWechatConfig.class); 143 144 145 // 1.拼接url(当前网页的URL,不包含#及其后面部分) 146 String url = getRequest().getRequestURL().toString().split("#")[0]; 147 String query = getRequest().getQueryString(); 148 if (StrUtil.isNotBlank(query)) { 149 url = url.concat("?").concat(query); 150 } 151 152 153// JsTicket jsTicket = JsTicketApi.getTicket(JsTicketApi.JsApiType.jsapi); 154 JsTicket jsTicket = WechatApis.getTicket(WechatApis.JsApiType.jsapi); 155 156 String _wxJsApiTicket = jsTicket.getTicket(); 157 158 String noncestr = StrUtil.uuid(); 159 String timestamp = (System.currentTimeMillis() / 1000) + ""; 160 161 Map<String, String> _wxMap = new TreeMap<String, String>(); 162 _wxMap.put("noncestr", noncestr); 163 _wxMap.put("timestamp", timestamp); 164 _wxMap.put("jsapi_ticket", _wxJsApiTicket); 165 _wxMap.put("url", url); 166 167 //拼接字符串 168 StringBuilder paramsBuilder = new StringBuilder(); 169 for (Map.Entry<String, String> param : _wxMap.entrySet()) { 170 paramsBuilder.append(param.getKey()).append("=").append(param.getValue()).append("&"); 171 } 172 String signString = paramsBuilder.substring(0, paramsBuilder.length() - 1); 173 174 //签名 175 String signature = HashKit.sha1(signString); 176 177 setAttr("wechatDebug", config.getDebug()); 178 setAttr("wechatAppId", getApiConfig().getAppId()); 179 setAttr("wechatNoncestr", noncestr); 180 setAttr("wechatTimestamp", timestamp); 181 setAttr("wechatSignature", signature); 182 183 } 184 185 public boolean isAllowVisit() { 186 HttpServletRequest req = getRequest(); 187 if (RequestUtil.isWechatPcBrowser(req)) { 188 return false; 189 } 190 191 return RequestUtil.isWechatBrowser(req); 192 } 193 194 @NotAction 195 public void doNotAllowVisitRedirect() { 196 /** 197 * 一般情况下,此方法是为了调整到其他页面,比如让用户扫描二维码之类的 198 * 由子类去实现 199 */ 200 renderText("jboot wechat error, cannot visit in this browser."); 201 } 202 203 204 public <T> T getCurrentUser() { 205 return getAttr(ATTR_USER_OBJECT); 206 } 207 208 209 /** 210 * 进行用户查找,找到返回该用户,找不到返回 null 211 * 返回的object(用户)通过 getCurrentUser 可以得到。 212 * 213 * @param openid 214 * @return 215 */ 216 public abstract Object doGetUserByOpenId(String openid); 217 218 /** 219 * 根据 apiResult 数据来保存或更新用户信息 220 * <p> 221 * 用户第一次访问的时候 222 * <p> 223 * <p> 224 * <p> 225 * apiResult内如如下: 226 * { 227 * "subscribe": 1, 228 * "openid": "o6_bmjrPTlm6_2sgVt7hMZOPfL2M", 229 * "nickname": "Band", 230 * "sex": 1, 231 * "language": "zh_CN", 232 * "city": "广州", 233 * "province": "广东", 234 * "country": "中国", 235 * "headimgurl": "http://wx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4 236 * eMsv84eavHiaiceqxibJxCfHe/0", 237 * "subscribe_time": 1382694957, 238 * "unionid": " o6_bmasdasdsad6_2sgVt7hMZOPfL" 239 * "remark": "", 240 * "groupid": 0, 241 * "tagid_list":[128,2] 242 * } 243 * 244 * @param apiResult 245 * @return 246 */ 247 248 public abstract Object doSaveOrUpdateUserByApiResult(ApiResult apiResult); 249}