单点登录
Redis连接池构建以及调试
声明:
public class RedisPool {
private static JedisPool pool;//Jedis连接池,加static是因为要在tomcat启动时就加载
private static Integer maxTotal = Integer.parseInt(PropertiesUtil.getProperty("redis.max.total", "20"));//设置和Redis Server的最大连接数
private static Integer maxIdle = Integer.parseInt(PropertiesUtil.getProperty("redis.max.idle", "20"));//JedisPool中最大的空闲实例数量
private static Integer minIdle = Integer.parseInt(PropertiesUtil.getProperty("redis.min.idle", "20"));//JedisPool中最小的空闲实例数量
private static Boolean testOnBorrow = Boolean.parseBoolean(PropertiesUtil.getProperty("redis.test.borrow", "true"));//在borrow一个jedis实例的时候,是否要进行验证操作,如果赋值true。则得到的jedis实例肯定是可以用的。
private static Boolean testOnReturn = Boolean.parseBoolean(PropertiesUtil.getProperty("redis.test.return", "true"));//在return一个jedis实例的时候,是否要进行验证操作,如果赋值true。则放回jedispool的jedis实例肯定是可以用的。
private static String redis1Ip = PropertiesUtil.getProperty("redis1.ip");
private static Integer redis1Port = Integer.parseInt(PropertiesUtil.getProperty("redis1.port"));
private static String redis2Ip = PropertiesUtil.getProperty("redis2.ip");
private static Integer redis2Port = Integer.parseInt(PropertiesUtil.getProperty("redis2.port"));
private static void initPool() {//后续后用一个内部静态块来调用这个方法,为了避免外部有人调用,设置成private
JedisPoolConfig config = new JedisPoolConfig();//只用config一个对象就可以初始化这个连接池
config.setMaxTotal(maxTotal);//不赋值源码中的默认值是8
config.setMaxIdle(maxIdle);//不赋值源码中的默认值是8
config.setMinIdle(minIdle);//不赋值源码中的默认值是0
config.setTestOnBorrow(testOnBorrow);//不赋值默认的是false
config.setTestOnReturn(testOnReturn);//不赋值默认的是false
config.setBlockWhenExhausted(true);//连接耗尽的时候,是否阻塞,false会抛出异常,true阻塞直到超时。默认为true。
pool = new JedisPool(config, redis1Ip, redis1Port, 1000 * 2);
}
static {
initPool();
}
//对外部的类,要把jedis中的内部的实例开放出去
public static Jedis getJedis() {
return pool.getResource();//从连接池获得实例
}
//把jedis放回连接池
public static void returnBrokenResource(Jedis jedis) {
pool.returnBrokenResource(jedis);
}
public static void returnResource(Jedis jedis) {
pool.returnResource(jedis);
}
public static void main(String[] args) {
Jedis jedis = pool.getResource();
for (int i = 0; i < 10; i++) {
jedis.set("key" + i, "value" + i);
}
returnResource(jedis);
// pool.destroy();//临时调用,销毁连接池中的所有连接
System.out.println("program is end");
}
}
JedisPoolConfig
JedisPool
Jedis API封装及调试
public static Long expire(String key,int exTime){
Jedis jedis = null;
Long result = null;
try {
jedis = RedisPool.getJedis();
result = jedis.expire(key,exTime);
} catch (Exception e) {
log.error("expire key:{} error",key,e);
RedisPool.returnBrokenResource(jedis);
return result;
}
RedisPool.returnResource(jedis);
return result;
}
//exTime的单位是秒
public static String setEx(String key,String value,int exTime){
Jedis jedis = null;
String result = null;
try {
jedis = RedisPool.getJedis();
result = jedis.setex(key,exTime,value);
} catch (Exception e) {
log.error("setex key:{} value:{} error",key,value,e);
RedisPool.returnBrokenResource(jedis);
return result;
}
RedisPool.returnResource(jedis);
return result;
}
public static String set(String key,String value){
Jedis jedis = null;
String result = null;
try {
jedis = RedisPool.getJedis();
result = jedis.set(key,value);
} catch (Exception e) {
log.error("set key:{} value:{} error",key,value,e);
RedisPool.returnBrokenResource(jedis);
return result;
}
RedisPool.returnResource(jedis);
return result;
}
public static String get(String key){
Jedis jedis = null;
String result = null;
try {
jedis = RedisPool.getJedis();
result = jedis.get(key);
} catch (Exception e) {
log.error("get key:{} error",key,e);
RedisPool.returnBrokenResource(jedis);
return result;
}
RedisPool.returnResource(jedis);
return result;
}
public static Long del(String key){
Jedis jedis = null;
Long result = null;
try {
jedis = RedisPool.getJedis();
result = jedis.del(key);
} catch (Exception e) {
log.error("del key:{} error",key,e);
RedisPool.returnBrokenResource(jedis);
return result;
}
RedisPool.returnResource(jedis);
return result;
}
Jackson 封装JsonUtil
@Slf4j
/**
* @Auther: wanglin
* @Date:9/4/2022 - 04 -09 -下午9:55
* @Description:com.mmall.util
* @version:1.0
*/
public class JsonUtil {//序列化与反序列化,用到Jackson工具包
private static ObjectMapper objectMapper = new ObjectMapper();
static{
//对象的所有字段全部列入
objectMapper.setSerializationInclusion(Inclusion.ALWAYS);
//取消默认转换timestamps形式
objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS,false);
//忽略空Bean转json的错误
objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS,false);
//所有的日期格式都统一为以下的样式,即yyyy-MM-dd HH:mm:ss
objectMapper.setDateFormat(new SimpleDateFormat(DateTimeUtil.STANDARD_FORMAT));
//反序列化
//忽略 在json字符串中存在,但是在java对象中不存在对应属性的情况。防止错误
objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES,false);
}
//把对象转换为json,也可以把json转化成对象
public static <T> String obj2String(T obj){
if(obj == null){
return null;
}
try {
return obj instanceof String ? (String)obj : objectMapper.writeValueAsString(obj);//obj是范型<T>类型,所以要强转
} catch (Exception e) {
log.warn("Parse Object to String error",e);
return null;
}
}
//用来封装可以返回格式化好的字符串
public static <T> String obj2StringPretty(T obj){
if(obj == null){
return null;
}
try {
return obj instanceof String ? (String)obj : objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
} catch (Exception e) {
log.warn("Parse Object to String error",e);
return null;
}
}
//把字符串转化成对象
public static <T> T string2Obj(String str,Class<T> clazz){// public static <T> 声明方法只有一个类型T,也可以看作这是一个泛型方法
if(StringUtils.isEmpty(str) || clazz == null){
return null;
}
try {
return clazz.equals(String.class)? (T)str : objectMapper.readValue(str,clazz);
} catch (Exception e) {
log.warn("Parse String to Object error",e);
return null;
}
}
//封装两个通用性非常强的反序列方法。
//应对比如将字符串反序列化为对象时,对象的类型是List<User> 需要传两个类型,不能传单独一个
public static <T> T string2Obj(String str, TypeReference<T> typeReference){
if(StringUtils.isEmpty(str) || typeReference == null){
return null;
}
try {
return (T)(typeReference.getType().equals(String.class)? str : objectMapper.readValue(str,typeReference));
} catch (Exception e) {
log.warn("Parse String to Object error",e);
return null;
}
}
//为社么要用问号:因为集合是一个类型,集合中的元素又是一个类型
public static <T> T string2Obj(String str,Class<?> collectionClass,Class<?>... elementClasses){//Class<?>代表集合类型,Class<?>...表示可变长参数,可以传一个参数也可以传多个
JavaType javaType = objectMapper.getTypeFactory().constructParametricType(collectionClass,elementClasses);
try {
return objectMapper.readValue(str,javaType);
} catch (Exception e) {
log.warn("Parse String to Object error",e);
return null;
}
}
public static void main(String[] args) {
User u1 = new User();
u1.setUsername("111");
u1.setEmail("asghdsafh");
String user1json = JsonUtil.obj2String(u1);
String user1jsonpretty = JsonUtil.obj2StringPretty(u1);
log.info("user1json:{}" , user1json);
log.info("user1jsonpretty {}", user1jsonpretty);
User user = JsonUtil.string2Obj(user1json, User.class);
System.out.println("**********************************");
}
}
Cookie 封装及使用
写、删、读
domain
@Slf4j
public class CookieUtil {
private final static String COOKIE_DOMAIN = ".happymmall.com";//xxx.happymmall.com的域名可以读到cookie
private final static String COOKIE_NAME = "mmall_login_token";//服务端种到客户端浏览器中的
//读cookie从request中读
public static String readLoginToken(HttpServletRequest request){
Cookie[] cks = request.getCookies();
if(cks != null){
for(Cookie ck : cks){
log.info("read cookieName:{},cookieValue:{}",ck.getName(),ck.getValue());
if(StringUtils.equals(ck.getName(),COOKIE_NAME)){
log.info("return cookieName:{},cookieValue:{}",ck.getName(),ck.getValue());
return ck.getValue();
}
}
}
return null;
}
//c和d能共享a的cookie, 也能共享e的cookie
//X:domain=".happymmall.com"
//a:A.happymmall.com cookie:domain=A.happymmall.com;path="/"
//b:B.happymmall.com cookie:domain=B.happymmall.com;path="/"
//c:A.happymmall.com/test/cc cookie:domain=A.happymmall.com;path="/test/cc"
//d:A.happymmall.com/test/dd cookie:domain=A.happymmall.com;path="/test/dd"
//e:A.happymmall.com/test cookie:domain=A.happymmall.com;path="/test"
//写cookie从response中写
//对response的回应
public static void writeLoginToken(HttpServletResponse response,String token){
Cookie ck = new Cookie(COOKIE_NAME,token);
ck.setDomain(COOKIE_DOMAIN);
ck.setPath("/");//代表设置在根目录
ck.setHttpOnly(true);//防止脚本攻击带来的信息泄露风险,规定不许通过脚本访问cookie
//单位是秒。
//如果这个maxage不设置的话,cookie就不会写入硬盘,而是写在内存。只在当前页面有效。
ck.setMaxAge(60 * 60 * 24 * 365);//如果是-1,代表永久
log.info("write cookieName:{},cookieValue:{}",ck.getName(),ck.getValue());
response.addCookie(ck);
}
public static void delLoginToken(HttpServletRequest request,HttpServletResponse response){
Cookie[] cks = request.getCookies();
if(cks != null){
for(Cookie ck : cks){
if(StringUtils.equals(ck.getName(),COOKIE_NAME)){
ck.setDomain(COOKIE_DOMAIN);
ck.setPath("/");
ck.setMaxAge(0);//设置成0,代表删除此cookie。
log.info("del cookieName:{},cookieValue:{}",ck.getName(),ck.getValue());
response.addCookie(ck);
return;
}
}
}
}
}
改造成用cookie作为key从Redis中找的模式
@Controller
@RequestMapping("/user/")
public class UserController {
@Autowired
private IUserService iUserService;//把Service中的接口注入
/**
* 用户登录
* @param username
* @param password
* @param session
* @return
*/
@RequestMapping(value = "login.do", method = RequestMethod.POST)
@ResponseBody
public ServerResponse<User> login(String username, String password, HttpSession session, HttpServletResponse httpServletResponse) {
//service -->mybatis --> dao
ServerResponse<User> response = iUserService.login(username, password);
if(response.isSuccess()) {
//session.setAttribute(Const.CURRENT_USER, response.getData());
CookieUtil.writeLoginToken(httpServletResponse, session.getId());
//获得sessionId值作为Redis的key,所获得的对象作为value序列化后存入Redis中
RedisShardedPoolUtil.setEx(session.getId(), JsonUtil.obj2String(response.getData()), Const.RedisCacheExtime.REDIS_SESSION_EXTIME);
//把登录信息保存到服务端的Redis中,在客户端使用
//在服务端写一个cookie
}
return response;
}
@RequestMapping(value = "logout.do", method = RequestMethod.POST)
@ResponseBody
public ServerResponse<User> logout(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
String loginToken = CookieUtil.readLoginToken(httpServletRequest);
CookieUtil.delLoginToken(httpServletRequest, httpServletResponse);//cookie的删除是把coolie的有效时间设置为e,返回给浏览器
//同样的,在Redis中也要把数据删除
RedisShardedPoolUtil.del(loginToken);
//session.removeAttribute(Const.CURRENT_USER);
return ServerResponse.createBySuccess();
}
@RequestMapping(value = "register.do", method = RequestMethod.POST)
@ResponseBody
public ServerResponse<String> register(User user) {
return iUserService.register(user);
}
@RequestMapping(value = "check_valid.do", method = RequestMethod.POST)
@ResponseBody
public ServerResponse<String> checkValid(String str, String type) {
return iUserService.checkValid(str, type);
}
@RequestMapping(value = "get_user_info.do", method = RequestMethod.POST)
@ResponseBody
public ServerResponse<User> getUserInfo (HttpServletRequest httpServletRequest) {
//User user = (User) session.getAttribute(Const.CURRENT_USER);
String loginToken = CookieUtil.readLoginToken(httpServletRequest);
if(StringUtils.isEmpty(loginToken)) {
return ServerResponse.createByErrorMessage("用户未登录,无法获取用户信息");
}
String userJsonStr = RedisShardedPoolUtil.get(loginToken);
User user = JsonUtil.string2Obj(userJsonStr, User.class);
if(user != null) {
//改造成为不从session中拿信息,而是从cookie中拿信息,去redis中查找
return ServerResponse.createBySuccess(user);
}
return ServerResponse.createByErrorMessage("用户未登录,无法获取用户信息");
}
@RequestMapping(value = "foeget_get_question.do", method = RequestMethod.POST)
@ResponseBody
public ServerResponse<String> forgetGetQuestion(String username) {
return iUserService.selectQuestion(username);
}
@RequestMapping(value = "foeget_check_answer.do", method = RequestMethod.POST)
@ResponseBody
public ServerResponse<String> forgetCheckAnswer(String username, String question, String answer) {
return iUserService.checkAnswer(username, question, answer);
}
@RequestMapping(value = "foeget_reset_password.do", method = RequestMethod.POST)
@ResponseBody
public ServerResponse<String> forgetResetPassword(String username, String passwordNew, String forgetToken) {
return iUserService.forgetResetPassword(username, passwordNew, forgetToken);
}
@RequestMapping(value = "reset_reset_password.do", method = RequestMethod.POST)
@ResponseBody
public ServerResponse<String> resetPassword(HttpSession session, String passwordOld, String passwordNew) {
User user = (User)session.getAttribute(Const.CURRENT_USER);
if(user == null)
return ServerResponse.createByErrorMessage("用户为登录");
return iUserService.resetPassword(passwordOld, passwordNew, user);
}
@RequestMapping(value = "update_information.do", method = RequestMethod.POST)
@ResponseBody
public ServerResponse<User> update_information(HttpServletRequest httpServletRequest, User user) {
//User currentUser = (User)session.getAttribute(Const.CURRENT_USER);
String loginToken = CookieUtil.readLoginToken(httpServletRequest);
if(StringUtils.isEmpty(loginToken)) {
return ServerResponse.createByErrorMessage("用户未登录,无法获取用户信息");
}
String userJsonStr = RedisShardedPoolUtil.get(loginToken);
User currentUser = JsonUtil.string2Obj(userJsonStr, User.class);
if(currentUser == null)
return ServerResponse.createByErrorMessage("用户未登录");
user.setId(currentUser.getId());
user.setUsername(user.getUsername());
ServerResponse<User> response = iUserService.updateInformation(user);
if(response.isSuccess()) {
//session.setAttribute(Const.CURRENT_USER, response.getData());
RedisShardedPoolUtil.setEx(loginToken, JsonUtil.obj2String(response.getData()), Const.RedisCacheExtime.REDIS_SESSION_EXTIME);
}
return response;
}
@RequestMapping(value = "get_information.do", method = RequestMethod.POST)
@ResponseBody
public ServerResponse<User> get_information(HttpServletRequest httpServletRequest) {
// User currentUser = (User)session.getAttribute(Const.CURRENT_USER);
String loginToken = CookieUtil.readLoginToken(httpServletRequest);
if(StringUtils.isEmpty(loginToken)) {
return ServerResponse.createByErrorMessage("用户未登录,无法获取用户信息");
}
String userJsonStr = RedisShardedPoolUtil.get(loginToken);
User currentUser = JsonUtil.string2Obj(userJsonStr, User.class);
if(currentUser == null)
return ServerResponse.createByErrorCodeMessage(ResponseCode.NEED_LOGIN.getCode(),"未登录,需要强制登录status = 10");
return iUserService.getInformation(currentUser.getId());
}
}
SessionExpireFilter 重置Session有效期
session的默认有效期是30分钟,我们需要在请求时将session的有效期重新设置成30分钟,否则就变成了只能登录30分钟
过滤所有.do结尾的,并在其中进行判断,把session的时间进行重置
public class SessionExpireFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest)servletRequest;
String loginToken = CookieUtil.readLoginToken(httpServletRequest);
if(StringUtils.isNotEmpty(loginToken)){
//判断logintoken是否为空或者"";
//如果不为空的话,符合条件,继续拿user信息
String userJsonStr = RedisShardedPoolUtil.get(loginToken);
User user = JsonUtil.string2Obj(userJsonStr,User.class);
if(user != null){
//如果user不为空,则重置session的时间,即调用expire命令
RedisShardedPoolUtil.expire(loginToken, Const.RedisCacheExtime.REDIS_SESSION_EXTIME);
}
}
filterChain.doFilter(servletRequest,servletResponse);//在web.xml中配置了filter,凡是以do结尾的, 就走拦截器,重置session时间
}
@Override
public void destroy() {
}
}
Guava Cache 迁移 Redis缓存
public ServerResponse<String> checkAnswer(String username, String question, String answer) {
int resultCount = userMapper.checkAnswer(username, question, answer);
if(resultCount > 0) {
//说明问题以及问题答案是这个用户的,并且答案是对的
String forgetToken = UUID.randomUUID().toString();
//TokenCache.setKey(TokenCache.TOKEN_PREFIX + username, forgetToken);
RedisShardedPoolUtil.setEx(Const.TOKEN_PREFIX+ username, forgetToken, 60 * 60 * 12);
return ServerResponse.createBySuccess(forgetToken);
}
return ServerResponse.createByErrorMessage("问题的答案错误");
}
public ServerResponse<String> forgetResetPassword(String username, String passwordNew, String forgetToken){
if(StringUtils.isNoneBlank(forgetToken))
return ServerResponse.createByErrorMessage("参数错误,token需要传递");
//需要校验一下username,因为拼接的时候是"token_"+username,如果username是空的,那么这个缓存就变成了一个没有变量控制的
//每个用户都可以拿到forgetToken
ServerResponse validResponse = this.checkValid(username, Const.USERNAME);
if(!validResponse.isSuccess()) {
return validResponse;
}
// String token = TokenCache.getKey(TokenCache.TOKEN_PREFIX+ username);
String token = RedisShardedPoolUtil.get(Const.TOKEN_PREFIX+ username);
if(StringUtils.isNoneBlank(token)) {
return ServerResponse.createByErrorMessage("token无效或者过期");
}
if(StringUtils.equals(forgetToken, token)) {
String md5password = MD5Util.MD5EncodeUtf8(passwordNew);
int rowCount = userMapper.updatePasswordByUsername(username, md5password);
if(rowCount > 0)
return ServerResponse.createBySuccessMessage("修改密码成功");
}else {
return ServerResponse.createByErrorMessage("token错误,请重新获取重置密码的token");
}
return ServerResponse.createByErrorMessage("修改密码失败");
}