關於支付寶即時到賬介面支付成功之後,支付寶回撥商戶介面時因session失效而導致回撥操作無法執行的問題
前兩天測試提交了一個很嚴重的bug,說是web前端呼叫支付寶二維碼掃碼支付成功之後,當網頁從支付寶頁面跳轉到我們自己網站頁面時session失效提示重新登入,如下圖:
原因是我的專案用了spring+shiro框架,session失效導致請求被攔截,附上攔截器的部分程式碼:
等登入進去之後,支付後的回撥操作沒有執行,公司網站的使用者資金賬戶資訊沒有做修改,但是使用者其實已經支付成功了。這倒是一個非常嚴重的問題了,於是去網上搜索了一下答案,發現都沒有適合我的。通過查閱支付寶即時到賬介面的開發文件,發現呼叫支付寶介面時可以傳一個叫 “extra_common_param” 的引數,截圖如下:
於是我在呼叫支付寶介面獲取二維碼的時候,就加上了一個引數 extra_common_param,這個引數傳遞的是當前會話的sessionId+System.currentTimeMillis() +out_trade_no 組成的字串(暫且命名為tempStr)。同時我以這個 tempStr 為key,sessionId為value(也可以是任意非空字串,或者是你按照自己演算法生成的字串,我這裡設計的是隻要能通過這個key從redis資料庫中找到不為空的value,就說明是有效的,因為key是唯一的),然後設定鍵值對有效期是3分鐘,這個時間是相對於我設定的支付寶二維碼有效時間而言的,因為我設定的支付寶二維碼有效時間是2分鐘,只要保證足夠的網路延遲時間就可以。
附上RedisUtils 工具類做參考:
package sy.util.redis; import org.apache.log4j.Logger; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; public class RedisUtils { private static final Logger logger = Logger.getLogger(RedisUtils.class); private static JedisPool jedisPool=null; static { JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(RedisConstant.MAX_ACTIVE); config.setMaxIdle(RedisConstant.MAX_IDLE); config.setMaxWaitMillis(RedisConstant.MAX_WAIT); config.setTestOnBorrow(RedisConstant.TEST_ON_BORROW); jedisPool = new JedisPool(config, RedisConstant.ADDR_ARRAY, RedisConstant.PORT, RedisConstant.TIMEOUT, RedisConstant.AUTH); logger.info("連線池初始化成功"); } // 獲取jedis private synchronized static Jedis getJedis() { if (jedisPool != null) { Jedis resource = jedisPool.getResource(); return resource; } else { return null; } } // 釋放Jedis資源 private static void returnResource(final Jedis jedis) { if (jedis != null) { jedisPool.returnResource(jedis); } } /** * * @Title: set * @Description: TODO(儲存字串鍵值對) * @param @param key * @param @param value * @param @return 引數 * @return String 返回型別 * @throws */ public static String set(String key, String value) { Jedis jedis = getJedis(); String str = null; try { str = jedis.set(key, value); logger.info("redis設定值成功,key是:" + key); } catch (Exception e) { logger.info("redis存值失敗,失敗原因:" + e.getMessage()); } finally { returnResource(jedis); } return str; } /** * @Title: set * @Description: TODO(儲存帶有效期的字串鍵值對) * @param @param key * @param @param value * @param @param seconds 設定超時失效時間,單位為秒 * @param @return 引數 * @return Long 如果成功地為該鍵設定了超時時間,返回 1,否則返回0 * @throws */ public static Long set(String key, String value, Integer seconds) { Jedis jedis = getJedis(); Long l = null; try { jedis.set(key, value); l = jedis.expire(key, seconds); logger.info("redis設定值成功,key是:" + key + "有效期是:" + seconds + "秒"); } catch (Exception e) { logger.info("redis存值失敗,失敗原因:" + e.getMessage()); } finally { returnResource(jedis); } return l; } /** * @Title: expire * @Description: TODO(設定一個key的生存時間) * @param @param key * @param @param seconds 設定超時過期時間,單位秒 * @param @return 引數 * @return Long 如果成功地為該鍵設定了超時時間,返回 1,否則返回0 * @throws */ public static Long expire(String key, Integer seconds) { Jedis jedis = getJedis(); Long l = null; try { l = jedis.expire(key, seconds); logger.info("redis生命週期設定成功,有效期是:" + seconds + "秒"); } catch (Exception e) { logger.info("redis設定生命週期失敗,失敗原因:" + e.getMessage()); } finally { returnResource(jedis); } return l; } /** * @Title: get * @Description: TODO(根據鍵獲取對應的字串值) * @param @param key * @param @return 引數 * @return String 返回型別 * @throws */ public static String get(String key){ Jedis jedis=getJedis(); String value=null; try{ value=jedis.get(key); logger.info("redis獲取值成功,獲取"+key+"的值是"+value); }catch(Exception e){ logger.info("redis獲取"+key+"的值失敗,失敗原因:" + e.getMessage()); }finally{ returnResource(jedis); } return value; } /** * @Title: del * @Description: TODO(刪除鍵值對) * @param @param key * @param @return 引數 * @return Long 返回1表示刪除成功,返回0表示刪除失敗 * @throws */ public static Long del(String key) { Jedis jedis = getJedis(); Long l = null; try { l = jedis.del(key); logger.info("redis刪值成功,被刪除的key是:" + key); } catch (Exception e) { logger.info("redis刪值失敗,失敗原因:" + e.getMessage()); } finally { returnResource(jedis); } return l; } /** * * @Title: getObject * @Description: TODO(從redis資料庫中獲取物件) * @param @param key * @param @return 引數 * @return Object 返回型別 * @throws */ public static Object getObject(String key) { Jedis jedis =getJedis(); Object obj=null; try { jedis = jedisPool.getResource(); byte[] bytes = jedis.get(key.getBytes()); if(bytes!=null) { obj= SerializeUtil.unserialize(bytes); logger.info("getObject獲取redis鍵值成功:key=" + key); } } catch (Exception e) { logger.info("getObject獲取redis鍵值異常:key=" + key + " cause:" + e.getMessage()); } finally { returnResource(jedis); } return obj; } /** * * @Title: setObject * @Description: TODO(將物件儲存到redis資料庫中) * @param @param key * @param @param value 要儲存的物件 * @param @return 引數 * @return String 返回字串"ok"表示儲存成功,返回其它表示儲存失敗 * @throws */ public static String setObject(String key, Object value) { Jedis jedis = getJedis(); String str=null; try { if(key!=null){ logger.info("setObject設定redis鍵值成功:key=" + key + " value=" + value); str=jedis.set(key.getBytes(), SerializeUtil.serialize(value)); } } catch (Exception e) { logger.info("setObject設定redis鍵值異常:key=" + key + " value=" + value + " cause:" + e.getMessage()); } finally { returnResource(jedis); } return str; } /** * * @Title: setObject * @Description: TODO(將物件儲存到redis資料庫中,並設定生命週期) * @param @param key * @param @param value 要儲存的物件 * @param @param seconds 鍵值對失效時間,單位為秒 * @param @return 引數 * @return Long 返回型別 成功返回1,失敗返回0 * @throws */ public static Long setObject(String key, Object value,Integer seconds) { Jedis jedis = getJedis(); Long l=null; try { if(key!=null){ jedis.set(key.getBytes(), SerializeUtil.serialize(value)); l = jedis.expire(key, seconds); logger.info("setObject設定redis鍵值成功:key=" + key + " value=" + value + " 生命週期:" + seconds); } } catch (Exception e) { logger.info("setObject設定redis鍵值異常:key=" + key + " value=" + value + " cause:" + e.getMessage()); } finally { returnResource(jedis); } return l; } /** * * @Title: delObject * @Description: TODO(刪除鍵值對物件) * @param @param key * @param @return 引數 * @return Long 返回1表示刪除成功,返回0表示刪除失敗 * @throws */ public static Long delObject(String key) { Jedis jedis = getJedis(); Long l=null; try { l=jedis.del(key.getBytes()); logger.info("delObject刪除redis鍵值成功:key=" + key); }catch(Exception e) { logger.info("delObject刪除redis鍵值異常:key=" + key+ " cause:" + e.getMessage()); }finally{ returnResource(jedis); } return l; } /** * * @Title: existsObject * @Description: TODO(判斷鍵值對是否存在,該方法適用於儲存的value是物件的) * @param @param key * @param @return 引數 * @return Boolean 返回true表示存在,返回false表示不存在 * @throws */ public static Boolean existsObject(String key) { Jedis jedis = getJedis(); Boolean b=false; try { b=jedis.exists(key.getBytes()); logger.info("redis呼叫exists方法判斷【"+key+"】是否存在,結果:" + b); }catch(Exception e) { logger.info("redis呼叫exists方法異常,原因:" + e.getMessage()); }finally{ returnResource(jedis); } return b; } /** * * @Title: exists * @Description: TODO(判斷鍵值對是否存在於redis資料庫中) * @param @param key * @param @return 引數 * @return Boolean true表示存在,false表示不存在 * @throws */ public static Boolean exists(String key) { Jedis jedis = getJedis(); Boolean b=false; try { b=jedis.exists(key); logger.info("redis呼叫exists方法判斷【"+key+"】是否存在,結果:" + b); }catch(Exception e) { logger.info("redis呼叫exists方法異常,原因:" + e.getMessage()); }finally{ returnResource(jedis); } return b; } }
RedisConstant .java類程式碼:
package sy.util.redis;
public class RedisConstant {
//測試環境
public static final String ADDR_ARRAY="127.0.0.1"; //Redis伺服器IP
public static final int PORT=6379; //Redis的埠號
public static final String AUTH = "123456789"; //訪問密碼
public static final int MAX_ACTIVE = 50; //可用連線例項的最大數目,預設值為8;如果賦值為-1,則表示不限制;如果pool已經分配了maxActive個jedis例項,則此時pool的狀態為exhausted(耗盡)。
public static final int MAX_IDLE = 50; //控制一個pool最多有多少個狀態為idle(空閒的)的jedis例項,預設值也是8。
public static final int MAX_WAIT = 3000; //等待可用連線的最大時間,單位毫秒,預設值為-1,表示永不超時。如果超過等待時間,則直接丟擲JedisConnectionException;
public static final int TIMEOUT = 10000; //超時時間
public static final boolean TEST_ON_BORROW = false; //在borrow一個jedis例項時,是否提前進行validate操作;如果為true,則得到的jedis例項均是可用的;
}
在我的攔截器中,新增如下判斷:
獲取支付寶二維碼之前,設定值如下:
被攔截的時候,從redis資料庫中能查詢到這個值,攔截器放行:
至此,問題解決啦!如有更好的解決方案,歡迎分享交流啊