对返回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();
}
}
}