1. 程式人生 > >關於支付寶即時到賬介面支付成功之後,支付寶回撥商戶介面時因session失效而導致回撥操作無法執行的問題

關於支付寶即時到賬介面支付成功之後,支付寶回撥商戶介面時因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資料庫中能查詢到這個值,攔截器放行:

至此,問題解決啦!如有更好的解決方案,歡迎分享交流啊