1. 程式人生 > >公眾號基礎的小程式搭建

公眾號基礎的小程式搭建

最近因為小程式的火爆,再加上老闆的要求。需要搭建並將部分公眾號功能開發到小程式,所以自己著手瞭解並搭建了小程式。(其中跳過很多坑,看過很多部落格。希望這個部落格可以幫助到同樣需求的童鞋把)

目錄: [TOC]

微信小程式和公眾號的區別、關聯

說起小程式和公眾號,其實基本上差不多,都有微信需要的openid,和對應的處理的微信介面(如推送模板訊息、支付等都需要對應的openid)最主要的還是unionId(這個需要在開放平臺上關聯公眾號和小程式,才能將兩個獨立的openId識別出相同的使用者,具體可以百度一下,這裡不做太多贅述)

封裝wx.request並且保持登陸的session,前提:使用者已經授權

雖然小程式可以直接獲取使用者openid作為使用者,之後獲取使用者授權關聯unionId的時候再同步資料。為了不操作麻煩,我的設計是在授權之後才能使用對應的功能。 好處: 方便統一處理某些情況,如請求session過期時自動重新整理,介面呼叫失敗顯示錯誤資訊。簡化寫法等。這邊根據我們的後臺業務做了一定的封裝,可以借鑑一下

/**
   * 封裝wx.request
   * param  obj 正常ajax內物件url,data等
   * param acFail方法,當活動返回錯誤-1時做的操作 func,這邊是後臺特定業務的返回值,做特殊處理
   */
  wxRequest: function(obj, acFail) {
    const that = this;
    let method = "POST"
    //更換方式
    if (obj.method) {
      method = obj.method
    }
    let header
    //根據請求方式,切換content-type型別
    if (method.toUpperCase() == "GET") {
      header = {
        'content-type': 'application/json'
      }
    } else {
      header = {
        'content-type': 'application/x-www-form-urlencoded'
      }
    }
    //放入服務端session,這個在登陸成功的介面中放置,作用:保持服務端session
    const sessionId = wx.getStorageSync("sessionId")
    if (sessionId) {
      header.cookie = 'SESSION=' + sessionId
    }
    // 封裝request
    wx.request({
      url: getApp().config.apiServer + obj.url,
      data: obj.data,
      method: method,
      header: header,
      success: function(res) {
        if (res.statusCode != 200) {//這邊是做一些請求驗證,200就是正常請求,可做擴充套件
          if (obj.fail && typeof obj.fail === "function") {
            obj.fail(res);
          } else {
            wx.showToast({
              title: '請求失敗,錯誤' + res.statusCode,
              icon: "none"
            })
          }
          return
        }
        const data = res.data
        if (data.status == 2) {//後臺登陸session過期或者未登陸特定返回值
          //這個方法用作使用者登陸,並且儲存對應的快取(包括sessionId)
          //session過期超時,需要重新整理session,並重新呼叫請求方法
          getApp().wxLogin(null, obj)
        } else if (data.status == 1) {//後臺普通錯誤返回值
          wx.showToast({
            title: data.msg,
            icon: "none",
            duration: 2000
          })
          console.log("error_url:", obj.url)
          console.log("err_msg:", data.msg)
        } else if (data.status == -1) {
          //定義活動失敗操作,活動這塊可以無視掉
          if (acFail && typeof acFail === 'function') {
            acFail()
          }
        } else {
        	if (obj.success&& typeof obj.success === "function") {
          		obj.success(data);
          	}
        }
      },
      fail: function(res) {
        if (obj.fail && typeof obj.fail === "function") {
          obj.fail(res);
        }
      }
    })
  },

其中有對錯誤情況的統一處理,重要的還是對session過期的處理,在下面會講到方法wxLogin:(這裡比較懶,有一種方式直接後臺解密通過wx.getUserInfo()獲取到使用者的encryptedData獲取使用者資訊,但是業務沒那麼精確,只需要unionId和使用者頭像暱稱等,所以這邊直接將基礎資訊userInfo傳到後端,有需要的童鞋可以自己研究一下,順便分享給我 d=====( ̄▽ ̄*)b)

/**
   * 微信登入方法,獲得code更新後臺session
   * userInfo 使用者資訊物件
   * callbackObj 請求過期時需要重新執行的請求引數
   * action 登入成功之後要做的事情
   */
  wxLogin: function(userInfo, callbackObj, action) {
    const jsonStr = userInfo ? JSON.stringify(userInfo) : ""
    wx.login({
      success: res => {
        getApp().wxRequest({
          url: "tokenHandle/takeWxJsapiSignature",
          data: {
            code: res.code,
            loginType: 1,
            mustLogin: "1",
            jsonStr: jsonStr
          },
          success: function(res) {
            if (res.status == 0) {
            	//保持會話,後臺需要傳過來的(必須是驗證登陸成功後才有)
              wx.setStorageSync("sessionId", res.data.sessionId)
              //如果有該物件,表示之前session過期,並且需要重新執行請求
              if (callbackObj) {
                getApp().wxRequest(callbackObj)
              }
              if (action && typeof action === 'function') {
                action()
              }
            }
            console.log(res)
          }
        })
      }
    })
  },

知識點: 1.wx.login(Object object) 呼叫介面獲取登入憑證(code)進而換取使用者登入態資訊,包括使用者的唯一標識(openid) 及本次登入的 會話金鑰(session_key)等。 2.封裝request中的回撥函式的使用和其中:請求過期後重新登陸需要再次執行一次過期的request

前端獲取使用者授權請求

因為微信後臺的升級,之後都不能使用wx.getUserInfo的方式直接調取獲取使用者資訊的彈窗,需要我們手動寫一個對應的button以點選按鈕的形式來提示獲取使用者資訊。

<button open-type="getUserInfo" bindgetuserinfo="bindGetUserInfo" plain='true'>

bindGetUserInfo,使用者點選成功/拒絕之後呼叫的方法:

/**
   * 使用者觸發登入操作
   */
  bindGetUserInfo: function(e) {
    if (e.detail.userInfo) {
      // 傳送 res.code 到後臺換取 openId, sessionKey, unionId
      const userInfo = e.detail.userInfo
      getApp().wxLogin(userInfo, null, function() {
        wx.showToast({
          title: '授權成功,即將返回',
          icon: "none",
          duration: 1000
        })
        setTimeout(function() {
          wx.navigateBack()
        }, 1000)
      })
    } else {
      console.log('執行到這裡,說明拒絕了授權')
      wx.showToast({
        title: "為了您更好的體驗,請先同意授權",
        icon: 'none',
        duration: 2000
      });
    }
  }

當然,需要做判斷,不能每次讓使用者點選按鈕。這邊我的處理方式是:

  1. wx.getSetting獲取配置是否使用者已經授權
  2. 已授權則直接wx.getUserInfo獲取使用者資訊,未授權則彈窗提示引導使用者點選/直接彈出按鈕提示使用者點選 ###後端處理小程式、公眾號使用專案自己的session+unionid登陸: 後臺獲取使用者資訊,關聯使用者的方法
/**
	 * 獲取簽名信息
	 * 
	 * @param code
	 *            微信引數
	 * @param pageUrl
	 *            重定向地址
	 * @param mustLogin
	 *            強制登陸
	 * @param loginType
	 *            登陸型別 0或者null公眾號,1小程式
	 * @return
	 */
	@RequestMapping("/takeWxJsapiSignature")
	@ResponseBody
	public ResultTO takeWxJsapiSignature(String code, String pageUrl, String mustLogin, Integer loginType,
			String jsonStr) {
		WechatUserInfo userInfo = null;
		try {
			//獲取AccessToken
			WxMpOAuth2AccessToken wmoat = (WxMpOAuth2AccessToken) getSessionAttribute(
					WeConstants.WEB_SESSION_ACCESS_TOKEN_KEY);
			if (wmoat == null && !StringUtils.isEmpty(code)) {
				if (loginType != null && loginType.intValue() == 1) {
					//如果是小程式,直接獲取前端發過來的使用者資訊json串
					if (StringUtils.isNotBlank(jsonStr)) {
						ObjectMapper mapper = new ObjectMapper();
						userInfo = mapper.readValue(jsonStr, WechatUserInfo.class);
					}
					//小程式特有的獲取openid,unionId方法
					wmoat = smallProgramService.jscode2session(code);
				} else {
					//公眾號獲取使用者資訊方法
					wmoat = wxMpService.oauth2getAccessToken(code);
				}
				setSessionAttribute(WeConstants.WEB_SESSION_ACCESS_TOKEN_KEY, wmoat);
			}
			BaseUser user = null;
			try {
				//最重要的,獲取使用者/關聯使用者的方法
				user = initUser(wmoat, code, loginType, userInfo);
			} catch (Exception e) {
				logger.info("init user fail ", e);
			}
			//公眾號使用的,session過期必須重新獲取使用者資訊
			if ("1".equals(mustLogin) && user == null) {
				String loginUrl = null;
				if (loginType != null && loginType.intValue() == 1) {
					loginUrl = smallProgramService.oauth2buildAuthorizationUrl(pageUrl,
							CommonConstants.OAUTH2_SCOPE_USER_INFO, null);
				} else {
					loginUrl = wxMpService.oauth2buildAuthorizationUrl(pageUrl, CommonConstants.OAUTH2_SCOPE_USER_INFO,
							null);
				}
				ResultTO res = new ResultTO();
				res.setStatus(3);// 未登入
				res.setData(loginUrl);
				return res;
			}
			if (wmoat == null) {
				throw new Exception("未鑑權");
			}
			if (pageUrl != null && pageUrl.indexOf("#") != -1) {
				pageUrl = pageUrl.substring(0, pageUrl.indexOf("#"));
			}
			WxJsapiSignature wxJsapiSignature = null;
			if (loginType != null && loginType.intValue() == 1) {
				String sessionId = request.getSession().getId();
				Map<String, Object> map = new HashMap<>();
				map.put("unionid", user.getWxUnionid());
				map.put("isMember", user.isMember());
				map.put("sessionId", sessionId);
				return new AccessSuccessResult(map);
			} else {
				wxJsapiSignature = wxMpService.createJsapiSignature(pageUrl);
				wxJsapiSignature.setUnionId(user.getWxUnionid());
				wxJsapiSignature.setIsMember(user.isMember());
				return new AccessSuccessResult(wxJsapiSignature);
			}
		} catch (Exception e) {
			e.printStackTrace();
			return new AccessErrorResult(e.getMessage());
		}

	}
//小程式特有的獲取openid,unionId方法
public WxMpOAuth2AccessToken jscode2session(String code) throws WxErrorException {
		String url = "https://api.weixin.qq.com/sns/jscode2session?";
		url += "appid=" + wxMpConfigStorage.getAppId();
		url += "&secret=" + wxMpConfigStorage.getSecret();
		url += "&js_code=" + code;
		url += "&grant_type=authorization_code";
		CloseableHttpClient httpClient = getHttpclient();
		try {
			RequestExecutor<String, String> executor = new SimpleGetRequestExecutor();
			String responseText = executor.execute(httpClient, httpProxy, url, null);
			return WxMpOAuth2AccessToken.fromJson(responseText);
		} catch (ClientProtocolException e) {
			throw new RuntimeException(e);
		} catch (IOException e) {
			throw new RuntimeException(e);
		} finally {
			if (httpClient != null) {
				try {
					httpClient.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}

公眾號獲取使用者資訊:

public WxMpOAuth2AccessToken oauth2getAccessToken(String code) throws WxErrorException {
		String url = "https://api.weixin.qq.com/sns/oauth2/access_token?";
		url += "appid=" + wxMpConfigStorage.getAppId();
		url += "&secret=" + wxMpConfigStorage.getSecret();
		url += "&code=" + code;
		url += "&grant_type=authorization_code";
		CloseableHttpClient httpClient = getHttpclient();
		try {
			RequestExecutor<String, String> executor = new SimpleGetRequestExecutor();
			String responseText = executor.execute(httpClient, httpProxy, url, null);
			return WxMpOAuth2AccessToken.fromJson(responseText);
		} catch (ClientProtocolException e) {
			throw new RuntimeException(e);
		} catch (IOException e) {
			throw new RuntimeException(e);
		} finally {
			if (httpClient != null) {
				try {
					httpClient.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}
private BaseUser initUser(WxMpOAuth2AccessToken wmoat, String code, Integer loginType, WechatUserInfo userInfo)
			throws Exception {
		//獲取request中的使用者資訊
		BaseUser user = (BaseUser) getSessionAttribute(WeConstants.WEB_SESSION_LOGIN_USER);
		if (user == null) {
			// 測試公眾號無法獲取Unionid只能使用openid,測試用
			if (wmoat != null && StringUtils.isBlank(wmoat.getUnionid())) {
				wmoat.setUnionid(wmoat.getOpenId());
			}
			//查詢資料庫中的使用者資訊,判斷是新增使用者,還是應該關聯使用者
			user = this.baseUserService.findBaseUserWxUnionId(wmoat.getUnionid());
			//判斷使用者關聯,如果某一個登陸方式不存在,並且現在已經獲取資訊了,就更新對應的資料並且跟新快取
			if (user != null) {
				if (loginType != null && loginType.intValue() == 1) {
					if (StringUtils.isBlank(user.getOpenIdS())) {
						BaseUser modifyUser = new BaseUser();
						modifyUser.setId(user.getId());
						modifyUser.setOpenIdS(wmoat.getOpenId());
						baseUserService.saveBaseUser(modifyUser);
						user.setOpenIdS(wmoat.getOpenId());
						setSessionAttribute(WeConstants.WEB_SESSION_LOGIN_USER, user);
					}
				} else {
					if (StringUtils.isBlank(user.getOpenId())) {
						BaseUser modifyUser = new BaseUser();
						modifyUser.setId(user.getId());
						modifyUser.setOpenId(wmoat.getOpenId());
						baseUserService.saveBaseUser(modifyUser);
						user.setOpenId(wmoat.getOpenId());
						setSessionAttribute(WeConstants.WEB_SESSION_LOGIN_USER, user);
					}
				}
			}

		}
		if (user == null) {
			// 新新增使用者
			BaseUser newUser = new BaseUser();
			if (loginType != null && loginType.intValue() == 1) {
				newUser.setWxUnionid(wmoat.getUnionid());
				newUser.setOpenIdS(wmoat.getOpenId());
				newUser.setNickName(userInfo.getNickName());
				newUser.setImage(userInfo.getAvatarUrl());
				newUser.setSex(userInfo.getGender().intValue() == 0 ? "女" : "男");
				newUser.setRegion(userInfo.getCountry() + " " + userInfo.getProvince() + " " + userInfo.getCity());
				newUser.setScore(50);
				newUser.setIsAdmin(false);
			} else {
			//公眾號獲取使用者,這塊程式碼需要詳細的可以扣我
				WxMpUser wxMpUser = (WxMpUser) getSessionAttribute(WeConstants.WEB_SESSION_WXMP_USER_KEY);
				if (wxMpUser == null) {
					if (!StringUtils.isEmpty(code) && wmoat != null
							&& !CommonConstants.OAUTH2_SCOPE_USER_INFO.equalsIgnoreCase(wmoat.getScope())) {
						wmoat = wxMpService.oauth2getAccessToken(code);
						setSessionAttribute(WeConstants.WEB_SESSION_ACCESS_TOKEN_KEY, wmoat);
					}
					if (wmoat != null) {
						if (loginType != null && loginType.intValue() == 1) {
							wxMpUser = smallProgramService.oauth2getUserInfo(wmoat, null);
						} else {
							wxMpUser = wxMpService.oauth2getUserInfo(wmoat, null);
						}
						setSessionAttribute(WeConstants.WEB_SESSION_WXMP_USER_KEY, wxMpUser);
					}
				}
				if (wxMpUser == null) {
					throw new Exception("獲取資訊失敗");
				}
				newUser.setWxUnionid(wmoat.getUnionid());
				newUser.setOpenId(wxMpUser.getOpenId());
				newUser.setNickName(wxMpUser.getNickname());
				newUser.setImage(wxMpUser.getHeadImgUrl());
				newUser.setSex(wxMpUser.getSex());
				newUser.setRegion(wxMpUser.getCountry() + " " + wxMpUser.getProvince() + " " + wxMpUser.getCity());
				newUser.setScore(50);
				newUser.setIsAdmin(false);
			}
			user = baseUserService.saveBaseUser(newUser);
		}
		setSessionAttribute(WeConstants.WEB_SESSION_LOGIN_USER, user);
		return user;
	}

獲取,刪除會話屬性

/**
		 * 
		 * 設定session屬性
		 * @Method setSessionAttribute
		 * @param request
		 * @param key
		 * @param valueObj void
		 * @Author gonghb
		 * @Date 2018年9月25日下午2:26:37
		 */
	 protected void setSessionAttribute(String key, Object valueObj){
		 setSessionAttribute(request, key, valueObj);
	 }
	 protected void setSessionAttribute(HttpServletRequest request, String key, Object valueObj){
		 request.getSession().setAttribute(key,valueObj);
	}
	/**
	 * 
	 * 獲取session屬性
	 * @Method getSessionAttribute
	 * @param arg1
	 * @return Object
	 * @Author gonghb
	 * @Date 2018年9月25日下午2:22:57
	 */
    protected Object getSessionAttribute(String key){
		 return getSessionAttribute(request,key);
	 }
    
	 protected Object getSessionAttribute(HttpServletRequest request,String key){
		return request.getSession().getAttribute(key);
	}

基本就是這樣了,等之後再加上小程式,公眾號的兩個支付上去。