公衆號基礎的小程序搭建(並處理小程序獲取不到用戶unionid問題)


最近因爲小程序的火爆,再加上老闆的要求。需要搭建並將部分公衆號功能開發到小程序,所以自己着手瞭解並搭建了小程序。(其中跳過很多坑,看過很多博客。希望這個博客可以幫助到同樣需求的童鞋把),博主做了一次更新2019.2.18日(解決關於小程序獲取不到unionid的問題)

微信小程序和公衆號的區別、關聯

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

微信小程序最重要的一點(!!!)

因爲這邊博主自己遇到,巨坑的一點。小程序通過公衆平臺關聯公衆號的時候。必須使用encryptedData獲取,否則只通過code再在後端通過接口直接獲取用戶信息時,未關注、登錄過公衆號的小程序獲取不到unionid
獲取unionid必須要通過解密encryptedData方式獲取到加密的用戶數據。
簡單說流程:客戶端獲取使用wx.login+wx.getUserInfo[參數withCredentials: true]再將code(wx.login的返回值)、encryptedData、iv發送到後端;後端操作:使用code獲取臨時的session_key,再用session_key,encryptedData、iv。解密獲取到unionid(這個一定不會空!!!)

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

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

 /**
   * 初始化方法,包含獲取用戶信息和登陸後臺(項目中所有請求都必須先通過該方法初始化)
   * callback 只有用戶授權成功並且登陸後才處理回調函數
   */
 wxInit: function(callback) {
    // 如果還沒監測配置成功
    if (!this.globalData.hasUserInfo) {
      wx.getSetting({
        success: res => {
          if (res.authSetting['scope.userInfo']) {
            // 已經授權,可以直接調用 getUserInfo獲取頭像暱稱,不會彈框
            //微信登錄
            this.wxLogin(null, callback)
          } else {
            wx.navigateTo({
              url: '../userInfo/index'
            });
          }
        }
      })
    } else {
      if (callback && typeof callback === "function") {
        callback()
      }
    }
  }
  
/**
   * 封裝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
    //根據請求方式,切換內容類型
    if (method.toUpperCase() == "GET") {
      header = {
        'content-type': 'application/json'
      }
    } else {
      header = {
        'content-type': 'application/x-www-form-urlencoded'
      }
    }
    //放入服務端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) {
      	//請求狀態不是200現實錯誤
        if (res.statusCode != 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過期超時,需要刷新session,並重新調用方法
          //判斷當前是否是未授權前請求,如果是,則直接返回
          wx.getSetting({
            success: res => {
              if (res.authSetting['scope.userInfo']) {
                getApp().wxLogin(obj)
              } else {
                //未授權請求
                console.log("未授權請求" + obj.url)
              }
            }
          })

        } 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 {
          obj.success(data);
        }
      },
      fail: function(res) {
        if (obj.fail && typeof obj.fail === "function") {
          obj.fail(res);
        }
      }
    })
  }

其中有對錯誤情況的統一處理,重要的還是對session過期的處理,d=====( ̄▽ ̄*)b

/**
   * 微信登錄方法,獲得code更新後臺session
   * userInfo 用戶信息對象
   * callbackObj 請求過期時需要重新執行的請求參數
   * action 登錄成功之後要做的事情
   */
  wxLogin: function(callbackObj, action) {
    wx.login({
      success: res => {
        const code = res.code
        wx.getUserInfo({
          withCredentials: true,
          lang: "zh_CN",
          success: res => {
            getApp().globalData.hasUserInfo = true
            const userInfo = res.userInfo
            getApp().globalData.userInfo = res.userInfo
            res.rawData = ""
            const jsonStr = JSON.stringify(res)
            //正式登陸
            getApp().wxRequest({
              url: "tokenHandle/smallProgramLogin",
              data: {
                code: code,
                loginType: 1,
                jsonStr: jsonStr
              },
              success: function(res) {
                if (res.status == 0) {
                  wx.setStorageSync("sessionId", res.data.sessionId)
                  wx.setStorageSync("isMember", res.data.isMember)
                  wx.setStorageSync("memberNum", res.data.memberNum)
                  if (res.data.isMember == "true") {
                    wx.setStorageSync("user", res.data.unionid)
                  } else {
                    wx.removeStorageSync("user")
                  }
                  wx.setStorageSync("userSign", res.data.unionid)
                  //如果有該對象,表示之前session過期,並且需要重新執行請求
                  if (callbackObj) {
                    getApp().wxRequest(callbackObj)
                  }
                  if (action && typeof action === 'function') {
                    action()
                  }
                }
                console.log(res)
              }
            })

            // 所以此處加入 callback 以防止這種情況
            if (this.userInfoReadyCallback) {
              this.userInfoReadyCallback(res)
            }
          }
        })
      }
    })
  }

知識點:
1.wx.login()
調用接口獲取登錄憑證(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(null, function() {
        wx.showToast({
          title: '授權成功!',
          icon: "none",
          duration: 500
        })
        setTimeout(function() {
          var route = getCurrentPages()[getCurrentPages().length - 2].route
          if (route == "pages/gssIndex/index") {
            let options = getCurrentPages()[getCurrentPages().length - 2].options
            if (options && options.togame) {
              wx.setStorageSync('togame', 'true')
            }
          }
          wx.navigateBack()
        }, 1000)
      })
    } else {
      console.log(333, '執行到這裏,說明拒絕了授權')
      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, HttpServletResponse response) {
		response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
		response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
		response.setHeader("Access-Control-Max-Age", "0");
		response.setHeader("Access-Control-Allow-Headers",
				"Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With,userId,token");
		response.setHeader("Access-Control-Allow-Credentials", "true");
		try {
			WxMpOAuth2AccessToken wmoat = (WxMpOAuth2AccessToken) getSessionAttribute(
					WeConstants.WEB_SESSION_ACCESS_TOKEN_KEY);
			if (wmoat == null && !StringUtils.isEmpty(code)) {
				wmoat = wxMpService.oauth2getAccessToken(code);
				setSessionAttribute(WeConstants.WEB_SESSION_ACCESS_TOKEN_KEY, wmoat);
			}
			BaseUser user = null;
			try {
				user = initUser(wmoat, code, 0, null);
			} catch (Exception e) {
				logger.info("init user fail ", e);
			}
			if ("1".equals(mustLogin) && user == null && (loginType == null || loginType == 0)) {// 只有公衆號才能url驗證
				String loginUrl = null;
				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) {//暫時刪除,不然小程序web-view是沒有鑑權的 throw new Exception("未鑑權"); }
			 */
			if (pageUrl != null && pageUrl.indexOf("#") != -1) {
				pageUrl = pageUrl.substring(0, pageUrl.indexOf("#"));
			}
			WxJsapiSignature wxJsapiSignature = null;
			wxJsapiSignature = wxMpService.createJsapiSignature(pageUrl);
			wxJsapiSignature.setUnionId(user.getId());
			wxJsapiSignature.setIsMember(user.isMember());
			wxJsapiSignature.setSessionId(request.getSession().getId());
			wxJsapiSignature.setName(user.getNickName());
			return new AccessSuccessResult(wxJsapiSignature);
		} catch (Exception e) {
			return checkWechatError(e);
		}

	}
	private ResultTO checkWechatError(Exception e) {
		try {
			String message = e.getMessage();
			if (StringUtils.isNotBlank(message) && message.indexOf("json:") >= 0) {// 判斷是否是微信的錯誤格式
				String errorMsg = message.split("json:")[1];
				Object parse = JSON.parse(errorMsg);
				if (parse instanceof Map) {
					@SuppressWarnings("rawtypes")
					Map map = (Map) parse;
					Object object = map.get("errcode");
					if (object instanceof Integer) {
						Integer error = (Integer) object;
						if (error.intValue() == 40163) {// code使用過或者code過期(不合法的 oauth_code)則返回過期
							AccessErrorResult accessErrorResult = new AccessErrorResult(e.getMessage());
							accessErrorResult.wechatOverdue(e.getMessage());
							return accessErrorResult;
						}
					}
				}
			}

		} catch (Exception e1) {
		}
		return new AccessErrorResult(e.getMessage());
	}

/**
	 * 小程序/小遊戲登陸
	 * 
	 * @param code
	 * @param loginType 1小程序,2小遊戲
	 * @param jsonStr 包含code、encryptedData、iv的json串
	 * @param response
	 * @return
	 */
	@SuppressWarnings("static-access")
	@RequestMapping("/smallProgramLogin")
	@ResponseBody
	public ResultTO smallProgramLogin(String code, Integer loginType, String jsonStr, HttpServletResponse response) {
		response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
		response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
		response.setHeader("Access-Control-Max-Age", "0");
		response.setHeader("Access-Control-Allow-Headers",
				"Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With,userId,token");
		response.setHeader("Access-Control-Allow-Credentials", "true");
		WxMpOAuth2AccessToken wmoat = (WxMpOAuth2AccessToken) getSessionAttribute(
				WeConstants.WEB_SESSION_ACCESS_TOKEN_KEY);
		try {
			WechatUserInfo userInfo = null;
			if (wmoat == null) {
				String encryptedData = null;
				String iv = null;
				String session_key = null;
				JSONObject data = JSONObject.parseObject(jsonStr);
				encryptedData = data.getString("encryptedData");
				iv = data.getString("iv");
				if (loginType != null && loginType == 1) {// 小程序
					wmoat = smallProgramService.jscode2session(code);
					session_key = wmoat.getSession_key();
				} else if (loginType != null && loginType == 2) {// 小遊戲
					wmoat = smallGameService.jscode2session(code);
					session_key = wmoat.getSession_key();
				}
				if (wmoat == null) {
					setSessionAttribute(WeConstants.WEB_SESSION_ACCESS_TOKEN_KEY, wmoat);
				}
				String userStr = getUserInfo(encryptedData, session_key, iv);
				JSONObject user = JSONObject.parseObject(userStr);
				String unionId = user.getString("unionId");
				userInfo = user.parseObject(userStr, WechatUserInfo.class);
				if (StringUtils.isNotBlank(unionId) && wmoat != null) {
					wmoat.setUnionid(unionId);
				}
			}
			BaseUser user = null;
			try {
				user = initUser(wmoat, code, loginType, userInfo);
			} catch (Exception e) {
				logger.info("init user fail ", e);
			}
			String sessionId = request.getSession().getId();
			Map<String, Object> map = new HashMap<>();
			map.put("unionid", user.getId());
			map.put("isMember", user.isMember());
			map.put("sessionId", sessionId);
			map.put("memberNum", user.getMemberNum());
			map.put("name", user.getNickName());
			return new AccessSuccessResult(map);
		} catch (WxErrorException e) {
			return checkWechatError(e);
		} catch (Exception e) {
			return checkWechatError(e);
		}
	}


/**
	 * 解密encryptedData,獲取用戶信息方法
	 */
	public String getUserInfo(String encryptedData, String sessionkey, String iv) {
		// 被加密的數據
		byte[] dataByte = Base64.decode(encryptedData);
		// 加密祕鑰
		byte[] keyByte = Base64.decode(sessionkey);
		// 偏移量
		byte[] ivByte = Base64.decode(iv);
		try {
			// 如果密鑰不足16位,那麼就補足. 這個if 中的內容很重要
			int base = 16;
			if (keyByte.length % base != 0) {
				int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0);
				byte[] temp = new byte[groups * base];
				Arrays.fill(temp, (byte) 0);
				System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
				keyByte = temp;
			}
			// 初始化
			Security.addProvider(new BouncyCastleProvider());
			Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
			SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
			AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
			parameters.init(new IvParameterSpec(ivByte));
			cipher.init(Cipher.DECRYPT_MODE, spec, parameters);// 初始化
			byte[] resultByte = cipher.doFinal(dataByte);
			if (null != resultByte && resultByte.length > 0) {
				String result = new String(resultByte, "UTF-8");
				return result;
			}
		} catch (NoSuchAlgorithmException e) {
			logger.error("NoSuchAlgorithmException :", e);
		} catch (NoSuchPaddingException e) {
			logger.error("NoSuchPaddingException :", e);
		} catch (InvalidParameterSpecException e) {
			logger.error("InvalidParameterSpecException :", e);
		} catch (IllegalBlockSizeException e) {
			logger.error("IllegalBlockSizeException :", e);
		} catch (BadPaddingException e) {
			logger.error("BadPaddingException :", e);
		} catch (UnsupportedEncodingException e) {
			logger.error("UnsupportedEncodingException :", e);
		} catch (InvalidKeyException e) {
			logger.error("InvalidKeyException :", e);
		} catch (InvalidAlgorithmParameterException e) {
			logger.error("InvalidAlgorithmParameterException :", e);
		} catch (NoSuchProviderException e) {
			logger.error("NoSuchProviderException :", e);
		}
		return null;
	}

//小程序特有的獲取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();
				}
			}
		}
	}
/**
	 * 初始化、關聯用戶的方法
	 * @param wmoat 微信返回構造的對象
	 * @param code 公衆號通過跳轉授權的code/小程序wx.login的code
	 * @param loginType 登陸類型 0/null公衆號,1小程序
	 * @param userInfo 小程序獲取過來的用戶對象
	 * @return
	 * @throws Exception
	 */
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;
	}

這邊主要講一下initUser方法,這個方法首先獲取session中存儲的用戶,當爲空(就是第一次登陸,或者session過期),然後再查詢數據庫。如果數據庫中的用戶不爲空,則校驗關聯。如果爲空,則使用微信的接口跟loginType根據不同的方法獲取對應的用戶數據,做用戶新增。

獲取,刪除會話屬性

/**
		 * 
		 * 設置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);
	}

小程序和公衆號支付、退款

這邊支付和退款,也就是對應的openid,appid,AppSecret的區別,這邊默認後臺都是使用的同一個,商戶也都會使用同一個,一般也都這樣操作。
對於支付和退款我先說一下思路把,我使用的是微信的支付sdk,所以我的做法是用兩個不同的實現類實現WXPayConfig,其中的商戶id,apikey,還有證書文件的讀取都是一樣的,在預支付訂單下單的時候,先判斷當前的登陸類型和接口也要加一個loginType來區分讀取的是小程序的openid還是公衆號的openid。
並且預支付id和商戶內部id(我們自己生成的uuid)也都拆分開來,用於某些操作(如:公衆號支付預支付訂單之後不付款,轉小程序付款之類的)另外,就是爲了方便退款,也需要將成功時的類型記錄下來,以便於後面退款使用正確的config實現類來退款


@Autowired
private MyConfig config;
@Autowired
private SmallProgramConfig spConfig;
/**
	 * 微信預訂單生成
	 * 
	 * @param wxOrder
	 *            初始化基本數據
	 * @param wxNotifyUrl
	 *            如:/order/wxNotifyOrder.do 微信回調接口
	 * @return
	 * @throws Exception
	 */
	public String wechatPrepay(WxPayOrder wxOrder, String wxNotifyUrl, Integer loginType) throws Exception {
		String settleId = wxOrder.getOutTradeNo();
		String ipAddr = wxOrder.getClientIp();
		String openId = wxOrder.getOpenid();
		String totalFee = wxOrder.getTotalFee();
		String body = wxOrder.getBody();
		// 切換小程序環境
		WXPay pay = null;
		if (loginType != null && loginType.intValue() == 1) {
			pay = new WXPay(spConfig, autoReport, useSandbox);
		} else {
			pay = new WXPay(config, autoReport, useSandbox);
		}

		SortedMap<String, String> reqData = new TreeMap<String, String>();
		reqData.put("attach", loginType == null ? "0" : loginType.toString());
		reqData.put("body", body);
		reqData.put("openid", openId);
		reqData.put("out_trade_no", settleId);
		reqData.put("spbill_create_ip", ipAddr);
		reqData.put("total_fee", totalFee);
		reqData.put("trade_type", "JSAPI");
		reqData.put("notify_url", concatChatServerUrl() + wxNotifyUrl);
		log.info("預支付數據:" + reqData);
		Map<String, String> unifiedOrder = pay.unifiedOrder(reqData);
		String prepayId = unifiedOrder.get("prepay_id");
		if (StringUtils.isEmpty(prepayId)) {
			if (loginType != null && loginType.intValue() == 1) {
				throw new Exception("小程序生成預支付訂單失敗");
			} else {
				throw new Exception("微信生成預支付訂單失敗");
			}
		}
		return prepayId;
	}
/**
	 * 退款
	 * 
	 * @param settleId 支付id
	 * @param refundId 退款id
	 * @param totalMoney 訂單總價
	 * @param refundMoney 退款價格
	 * @param refundDesc 退款備註
	 * @param wxRefundNotifyUrl 異步回調通知地址
	 * @return
	 * @throws Exception
	 */
	public Map<String, String> refund(String settleId, String refundId, double totalMoney, String wxRefundNotifyUrl,
			double refundMoney, String refundDesc, Integer loginType) throws Exception {
		String totalFee = String.valueOf(OrderUtils.moneyToFen(totalMoney));
		String refundFee = String.valueOf(OrderUtils.moneyToFen(refundMoney));
		WXPay pay = null;
		if (loginType != null && loginType.intValue() == 1) {
			pay = new WXPay(spConfig, wxRefundNotifyUrl, autoReport, useSandbox);
		} else {
			pay = new WXPay(config, wxRefundNotifyUrl, autoReport, useSandbox);
		}
		SortedMap<String, String> reqData = new TreeMap<String, String>();
		reqData.put("out_trade_no", settleId);
		reqData.put("out_refund_no", refundId);
		reqData.put("total_fee", totalFee);
		reqData.put("refund_fee", refundFee);
		if (StringUtils.isNotBlank(refundDesc)) {
			reqData.put("refund_desc", refundDesc);
		}
		return pay.refund(reqData);
	}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章