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}