【商城秒殺項目】-- 對返回json結果的封裝、通用緩存Key的設計與封裝

對返回json結果的封裝

在MVC模式中Controller接口類裏面一般有兩種返回值:rest api的json格式數據、頁面;在前後端開發模式中,後端開發人員與前端開發人員會對接口返回值進行約定,返回值一般是以rest api的json格式數據爲主,比如下面這種格式:

{
    "code": 200 //狀態碼:不同的狀態碼有着不同的含義
    "msg": "success" //提示信息:代表狀態碼對應的相應信息
    "data": "data" //數據:可能是對象,也可能是一個數組
}

對後端而言,這其實就是返回一個Map,Map裏面包含三個鍵值對,爲了減少代碼的重複性,有必要對返回的json結果集進行封裝

  • 建一個json結果集的封裝類(Result.java):
package com.javaxl.miaosha_05.result;

public class Result<T> {

    private int code;
    private String msg;
    //因爲返回的數據不知道是什麼類型,所以定義一個泛型
    private T data;

    /**
     * 成功的時候調用
     */
    public static <T> Result<T> success(T data) {
        return new Result<T>(data);
    }

    /**
     * 失敗的時候調用
     */
    public static <T> Result<T> error(CodeMsg codeMsg) {
        return new Result<T>(codeMsg);
    }

    private Result(T data) {
        this.data = data;
    }

    private Result(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    private Result(CodeMsg codeMsg) {
        if (codeMsg != null) {
            this.code = codeMsg.getCode();
            this.msg = codeMsg.getMsg();
        }
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

此外,還需要對處理結果成功或者失敗的情況做一個狀態碼與提示信息的封裝,成功的時候,返回數據;失敗的時候,返回狀態碼和提示信息,便於前期的開發與後續的維護

  • 建一個狀態碼與提示信息的封裝類(CodeMsg .java):
package com.javaxl.miaosha_05.result;

public class CodeMsg {

    private int code;
    private String msg;

    //通用的錯誤碼
    public static CodeMsg SUCCESS = new CodeMsg(200, "success");
    public static CodeMsg SERVER_ERROR = new CodeMsg(500, "服務端異常");
    public static CodeMsg BIND_ERROR = new CodeMsg(500101, "參數校驗異常:%s");
    public static CodeMsg REQUEST_ILLEGAL = new CodeMsg(500102, "請求非法");
    public static CodeMsg ACCESS_LIMIT_REACHED = new CodeMsg(500104, "訪問太頻繁!");
    //登錄模塊5002XX
    public static CodeMsg SESSION_ERROR = new CodeMsg(500210, "Session不存在或者已經失效");
    public static CodeMsg PASSWORD_EMPTY = new CodeMsg(500211, "登錄密碼不能爲空");
    public static CodeMsg MOBILE_EMPTY = new CodeMsg(500212, "手機號不能爲空");
    public static CodeMsg MOBILE_ERROR = new CodeMsg(500213, "手機號格式錯誤");
    public static CodeMsg MOBILE_NOT_EXIST = new CodeMsg(500214, "手機號不存在");
    public static CodeMsg PASSWORD_ERROR = new CodeMsg(500215, "密碼錯誤");

    //商品模塊5003XX

    //訂單模塊5004XX
    public static CodeMsg ORDER_NOT_EXIST = new CodeMsg(500400, "訂單不存在");

    //秒殺模塊5005XX
    public static CodeMsg MIAO_SHA_OVER = new CodeMsg(500500, "商品已經秒殺完畢");
    public static CodeMsg REPEATE_MIAOSHA = new CodeMsg(500501, "不能重複秒殺");
    public static CodeMsg MIAOSHA_FAIL = new CodeMsg(500502, "秒殺失敗");

    private CodeMsg() {
    }

    private CodeMsg(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public CodeMsg fillArgs(Object... args) {
        int code = this.code;
        String message = String.format(this.msg, args);
        return new CodeMsg(code, message);
    }

    @Override
    public String toString() {
        return "CodeMsg [code=" + code + ", msg=" + msg + "]";
    }
}

然後在controller層就可以這樣返回結果:

通用緩存Key的設計與封裝

設計與封裝通用緩存Key的好處:當項目中的模塊越來越多的時候,需要存的緩存也越來越多,比如商品ID、訂單ID、用戶ID等,此時若是ID出現重複,將可能給系統帶來錯誤;那麼可以使用KeyPrefix來更好的操作和管理緩存中對應的key,即給不同模塊的key加一個前綴,以達到以下效果:

好處:將前綴+key一起作爲redis裏面真正的key,這樣不同模塊之間就不會重複;不同功能或者不同模塊的前綴不同,即使有同名key出現,那麼前綴不同,並不會引起key衝突被其他功能覆蓋的情況

可使用一個模板方法模式來進行封裝:

  • 接口代碼:
package com.javaxl.miaosha_05.redis;

/**
 * 做緩存的前綴接口
 */
public interface KeyPrefix {
		
	public int expireSeconds();
	
	public String getPrefix();
}
  • 抽象類代碼(簡單的實現一下KeyPrefix,定義成抽象類的原因:防止不小心被創建,我們不希望BasePrefix被實例化,因爲抽象類不允許實例化,我們只希望它被繼承,不同模塊的前綴類都繼承它):
package com.javaxl.miaosha_05.redis;

//定義成抽象類
public abstract class BasePrefix implements KeyPrefix {

    private int expireSeconds;

    private String prefix;

    public BasePrefix(String prefix) {//0代表永不過期
        this(0, prefix);
    }

    public BasePrefix(int expireSeconds, String prefix) {//覆蓋了默認的構造函數
        this.expireSeconds = expireSeconds;
        this.prefix = prefix;
    }

    public int expireSeconds() {//默認0代表永不過期
        return expireSeconds;
    }

    public String getPrefix() {
        //前綴爲類名:+prefix
        String className = getClass().getSimpleName();
        return className + ":" + prefix;
    }
}

注:該類有2種不同的構造方法用於繼承,一個只帶前綴名,一個帶前綴名和過期時間。當實現public BasePrefix(String prefix)的時候,我們將默認這個key不會失效,因爲有一些場景,我們不希望key失效,但是有些場景我們需要設置key的有效期

  • 具體實現類代碼:

UserKey.java繼承了super(prefix),即public BasePrefix(String prefix),那麼代表user的key的過期時間爲不會過期:

package com.javaxl.miaosha_05.redis;

public class UserKey extends BasePrefix {

    private UserKey(String prefix) {
        super(prefix);
    }

    public static UserKey getById = new UserKey("id");
    public static UserKey getByName = new UserKey("name");
}

MiaoshaUserKey.java繼承了super(expireSeconds,prefix),即public BasePrefix(int expireSeconds, String prefix),那麼miaoshaUser的key可以設置有效期時間爲2天:

package com.javaxl.miaosha_05.redis;

public class MiaoshaUserKey extends BasePrefix {

    public static final int TOKEN_EXPIRE = 3600 * 24 * 2;

    private MiaoshaUserKey(int expireSeconds, String prefix) {
        super(expireSeconds, prefix);
    }

    public static MiaoshaUserKey token = new MiaoshaUserKey(TOKEN_EXPIRE, "tk");
    public static MiaoshaUserKey getById = new MiaoshaUserKey(0, "id");
}

然後具體可這樣使用:

  • 其他所需代碼如下:

RedisPoolFactory.java(通過配置文件,生成Jedis連接池(配置),方便在RedisService中調用):

package com.javaxl.miaosha_05.redis;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Service;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

@Service
public class RedisPoolFactory {

    @Autowired
    RedisConfig redisConfig;

    @Bean
    public JedisPool JedisPoolFactory() {
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxIdle(redisConfig.getPoolMaxIdle());
        poolConfig.setMaxTotal(redisConfig.getPoolMaxTotal());
        poolConfig.setMaxWaitMillis(redisConfig.getPoolMaxWait() * 1000);
        JedisPool jp = new JedisPool(poolConfig, redisConfig.getHost(), redisConfig.getPort(),
                redisConfig.getTimeout() * 1000, redisConfig.getPassword(), 0);
        return jp;
    }
}

RedisConfig.java(將配置文件裏面前綴爲"redis"的配置項,與類裏面的屬性對應起來):

package com.javaxl.miaosha_05.redis;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
//指定配置文件裏面前綴爲"redis"的配置項,與配置項裏面的屬性對應起來
@ConfigurationProperties(prefix = "redis")
public class RedisConfig {
    private String host;
    private int port;
    private int timeout;//秒
    private String password;
    private int poolMaxTotal;
    private int poolMaxIdle;
    private int poolMaxWait;//秒

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public int getTimeout() {
        return timeout;
    }

    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public int getPoolMaxTotal() {
        return poolMaxTotal;
    }

    public void setPoolMaxTotal(int poolMaxTotal) {
        this.poolMaxTotal = poolMaxTotal;
    }

    public int getPoolMaxIdle() {
        return poolMaxIdle;
    }

    public void setPoolMaxIdle(int poolMaxIdle) {
        this.poolMaxIdle = poolMaxIdle;
    }

    public int getPoolMaxWait() {
        return poolMaxWait;
    }

    public void setPoolMaxWait(int poolMaxWait) {
        this.poolMaxWait = poolMaxWait;
    }
}

RedisService.java(提供關於redis的服務方法):

package com.javaxl.miaosha_05.redis;

import com.alibaba.fastjson.JSON;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.ScanParams;
import redis.clients.jedis.ScanResult;

import java.util.ArrayList;
import java.util.List;

@Service
public class RedisService {

    @Autowired
    JedisPool jedisPool;

    /**
     * 獲取單個對象
     */
    public <T> T get(KeyPrefix prefix, String key, Class<T> clazz) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            //生成真正的key
            String realKey = prefix.getPrefix() + key;
            String str = jedis.get(realKey);
            T t = stringToBean(str, clazz);
            return t;
        } finally {
            returnToPool(jedis);
        }
    }

    /**
     * 設置對象
     */
    public <T> boolean set(KeyPrefix prefix, String key, T value) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            String str = beanToString(value);
            if (str == null || str.length() <= 0) {
                return false;
            }
            //生成真正的key
            String realKey = prefix.getPrefix() + key;
            int seconds = prefix.expireSeconds();
            if (seconds <= 0) {//有效期:代表不過期,這樣纔去設置
                jedis.set(realKey, str);
            }
            else {//沒有設置過期時間,那麼自己設置
                jedis.setex(realKey, seconds, str);
            }
            return true;
        } finally {
            returnToPool(jedis);
        }
    }

    /**
     * 判斷key是否存在
     */
    public <T> boolean exists(KeyPrefix prefix, String key) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            //生成真正的key
            String realKey = prefix.getPrefix() + key;
            return jedis.exists(realKey);
        } finally {
            returnToPool(jedis);
        }
    }

    /**
     * 刪除
     */
    public boolean delete(KeyPrefix prefix, String key) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            //生成真正的key
            String realKey = prefix.getPrefix() + key;
            long ret = jedis.del(key);
            return ret > 0;
        } finally {
            returnToPool(jedis);
        }
    }

    /**
     * 增加值
     */
    public <T> Long incr(KeyPrefix prefix, String key) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            //生成真正的key
            String realKey = prefix.getPrefix() + key;
            return jedis.incr(realKey);
        } finally {
            returnToPool(jedis);
        }
    }

    /**
     * 減少值
     */
    public <T> Long decr(KeyPrefix prefix, String key) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            //生成真正的key
            String realKey = prefix.getPrefix() + key;
            return jedis.decr(realKey);
        } finally {
            returnToPool(jedis);
        }
    }

    /**
     * 將Bean對象轉換爲字符串類型
     */
    public static <T> String beanToString(T value) {
        if (value == null) {
            return null;
        }
        Class<?> clazz = value.getClass();
        if (clazz == int.class || clazz == Integer.class) {
            return "" + value;
        } else if (clazz == String.class) {
            return (String) value;
        } else if (clazz == long.class || clazz == Long.class) {
            return "" + value;
        } else {
            return JSON.toJSONString(value);
        }
    }

    /**
     * 將字符串轉換爲Bean對象
     */
    public static <T> T stringToBean(String str, Class<T> clazz) {
        if (str == null || str.length() <= 0 || clazz == null) {
            return null;
        }
        if (clazz == int.class || clazz == Integer.class) {
            return (T) Integer.valueOf(str);
        } else if (clazz == String.class) {
            return (T) str;
        } else if (clazz == long.class || clazz == Long.class) {
            return (T) Long.valueOf(str);
        } else {
            return JSON.toJavaObject(JSON.parseObject(str), clazz);
        }
    }

    public boolean delete(KeyPrefix prefix) {
        if (prefix == null) {
            return false;
        }
        List<String> keys = scanKeys(prefix.getPrefix());
        if (keys == null || keys.size() <= 0) {
            return true;
        }
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            jedis.del(keys.toArray(new String[0]));
            return true;
        } catch (final Exception e) {
            e.printStackTrace();
            return false;
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    public List<String> scanKeys(String key) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            List<String> keys = new ArrayList<String>();
            String cursor = "0";
            ScanParams sp = new ScanParams();
            sp.match("*" + key + "*");
            sp.count(100);
            do {
                ScanResult<String> ret = jedis.scan(cursor, sp);
                List<String> result = ret.getResult();
                if (result != null && result.size() > 0) {
                    keys.addAll(result);
                }
                //再處理cursor
                cursor = ret.getCursor();
            } while (!cursor.equals("0"));
            return keys;
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    public static void main(String[] args) {
        RedisService redisService = new RedisService();
        for (String scanKey : redisService.scanKeys(OrderKey.getMiaoshaOrderByUidGid.getPrefix())) {
            System.out.println(scanKey);
        }
    }

    private void returnToPool(Jedis jedis) {
        if (jedis != null) {
            jedis.close();
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章