diff --git a/pom.xml b/pom.xml index b9fd511..a1e84e0 100644 --- a/pom.xml +++ b/pom.xml @@ -21,6 +21,10 @@ org.springframework.boot spring-boot-starter-data-jpa + + org.springframework.boot + spring-boot-starter-data-redis + org.springframework.boot spring-boot-starter-web diff --git a/src/main/java/com/codemagician/onlinelibrary/domain/entity/RoleDO.java b/src/main/java/com/codemagician/onlinelibrary/domain/entity/RoleDO.java index beab1b1..d5d4622 100644 --- a/src/main/java/com/codemagician/onlinelibrary/domain/entity/RoleDO.java +++ b/src/main/java/com/codemagician/onlinelibrary/domain/entity/RoleDO.java @@ -5,6 +5,8 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; + /** * @author Siuyun Yip * @version 1.0 @@ -14,7 +16,7 @@ @NoArgsConstructor @Entity @Table(name = "roles") -public class RoleDO { +public class RoleDO implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/com/codemagician/onlinelibrary/domain/entity/UserDO.java b/src/main/java/com/codemagician/onlinelibrary/domain/entity/UserDO.java index ab6dc1d..a3e1ebf 100644 --- a/src/main/java/com/codemagician/onlinelibrary/domain/entity/UserDO.java +++ b/src/main/java/com/codemagician/onlinelibrary/domain/entity/UserDO.java @@ -6,6 +6,7 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; import java.util.HashSet; import java.util.Set; @@ -15,6 +16,7 @@ * @date 2023/8/31 15:39 */ + @Data @NoArgsConstructor @Entity @@ -23,7 +25,7 @@ @UniqueConstraint(columnNames = "username"), @UniqueConstraint(columnNames = "email") }) -public class UserDO { +public class UserDO implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/com/codemagician/onlinelibrary/security/jwt/AuthTokenFilter.java b/src/main/java/com/codemagician/onlinelibrary/security/jwt/AuthTokenFilter.java index 76866f6..15324c0 100644 --- a/src/main/java/com/codemagician/onlinelibrary/security/jwt/AuthTokenFilter.java +++ b/src/main/java/com/codemagician/onlinelibrary/security/jwt/AuthTokenFilter.java @@ -43,7 +43,6 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse String jwt = parseJwt(request); if (jwt != null && jwtUtils.validateJwtToken(jwt)) { String username = jwtUtils.getUserNameFromJwtToken(jwt); - // TODO fetch user data from Redis UserDetails user = userDetailsService.loadUserByUsername(username); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities()); diff --git a/src/main/java/com/codemagician/onlinelibrary/security/services/UserDetailsServiceImpl.java b/src/main/java/com/codemagician/onlinelibrary/security/services/UserDetailsServiceImpl.java index 933cb48..5c45f48 100644 --- a/src/main/java/com/codemagician/onlinelibrary/security/services/UserDetailsServiceImpl.java +++ b/src/main/java/com/codemagician/onlinelibrary/security/services/UserDetailsServiceImpl.java @@ -3,7 +3,10 @@ import com.codemagician.onlinelibrary.domain.entity.UserDO; import com.codemagician.onlinelibrary.dao.repo.UserRepository; import com.codemagician.onlinelibrary.common.exception.AccessException; +import com.codemagician.onlinelibrary.util.RedisUtil; +import jakarta.annotation.Resource; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.stereotype.Service; @@ -23,10 +26,17 @@ public class UserDetailsServiceImpl implements UserDetailsService { @Autowired UserRepository userRepository; + @Resource + private RedisUtil redisUtil; + @Override @Transactional public UserDetails loadUserByUsername(String username) throws AccessException { - UserDO user = userRepository.findByUsernameWithRoles(username).orElseThrow(() -> new AccessException("User Not Found with username: " + username)); + UserDO user; + if ((user = (UserDO)redisUtil.get(username)) == null) { + user = userRepository.findByUsernameWithRoles(username).orElseThrow(() -> new AccessException("User Not Found with username: " + username)); + redisUtil.set(username, user); + } return UserDetailsImpl.build(user); } diff --git a/src/main/java/com/codemagician/onlinelibrary/util/RedisUtil.java b/src/main/java/com/codemagician/onlinelibrary/util/RedisUtil.java new file mode 100644 index 0000000..aa0f8c1 --- /dev/null +++ b/src/main/java/com/codemagician/onlinelibrary/util/RedisUtil.java @@ -0,0 +1,620 @@ +package com.codemagician.onlinelibrary.util; + +import jakarta.annotation.Resource; +import org.springframework.data.redis.core.BoundListOperations; +import org.springframework.data.redis.core.RedisCallback; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +/** + * @author Siuyun Yip + * @version 1.0 + * @date 2023/10/1 16:55 + */ + +@Component +public class RedisUtil { + + @Resource + private RedisTemplate redisTemplate; + + /** + * set cache valid time + * + * @param key + * @param time + * @return + */ + public boolean expire(String key, long time) { + try { + if (time > 0) { + redisTemplate.expire(key, time, TimeUnit.SECONDS); + } + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + public long getExpire(String key) { + return redisTemplate.getExpire(key, TimeUnit.SECONDS); + } + + public boolean hasKey(String key) { + try { + return redisTemplate.hasKey(key); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * delete cache + * + * @param key + */ + public void del(String... key) { + if (key != null && key.length > 0) { + if (key.length == 1) { + redisTemplate.delete(key[0]); + } else { + redisTemplate.delete((Collection) CollectionUtils.arrayToList(key)); + } + } + } + + public Object get(String key) { + return key == null ? null : redisTemplate.opsForValue().get(key); + } + + public boolean set(String key, Object value) { + try { + redisTemplate.opsForValue().set(key, value); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * set cache with timeout + * + * @param key + * @param value + * @param time + * @return + */ + public boolean set(String key, Object value, long time) { + try { + if (time > 0) { + redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); + } else { + set(key, value); + } + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * distributed lock + * + * @param key locked key + * @param lockExpireMils expire time without unlock + * @return + */ + public boolean setNx(String key, Long lockExpireMils) { + return (boolean) redisTemplate.execute((RedisCallback) connection -> { + return connection.setNX(key.getBytes(), String.valueOf(System.currentTimeMillis() + lockExpireMils + 1).getBytes()); + }); + } + + /** + * increase value for key + * + * @param key + * @param delta value to increase + * @return + */ + public long incr(String key, long delta) { + if (delta < 0) { + throw new RuntimeException("increment element has to be greater than 0"); + } + + return redisTemplate.opsForValue().increment(key, delta); + } + + public long decr(String key, long delta) { + if (delta < 0) { + throw new RuntimeException("decrement element has to be greater than 0"); + } + return redisTemplate.opsForValue().increment(key, -delta); + } + + /** + * HashGet + * + * @param key 键 不能为null + * @param item 项 不能为null + * @return 值 + */ + public Object hget(String key, String item) { + return redisTemplate.opsForHash().get(key, item); + } + + /** + * 获取hashKey对应的所有键值 + * + * @param key 键 + * @return 对应的多个键值 + */ + public Map hmget(String key) { + return redisTemplate.opsForHash().entries(key); + } + + /** + * HashSet + * + * @param key 键 + * @param map 对应多个键值 + * @return true 成功 false 失败 + */ + public boolean hmset(String key, Map map) { + try { + redisTemplate.opsForHash().putAll(key, map); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * HashSet 并设置时间 + * + * @param key 键 + * @param map 对应多个键值 + * @param time 时间(秒) + * @return true成功 false失败 + */ + public boolean hmset(String key, Map map, long time) { + try { + redisTemplate.opsForHash().putAll(key, map); + if (time > 0) { + expire(key, time); + } + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 向一张hash表中放入数据,如果不存在将创建 + * + * @param key 键 + * @param item 项 + * @param value 值 + * @return true 成功 false失败 + */ + public boolean hset(String key, String item, Object value) { + try { + redisTemplate.opsForHash().put(key, item, value); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 向一张hash表中放入数据,如果不存在将创建 + * + * @param key 键 + * @param item 项 + * @param value 值 + * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间 + * @return true 成功 false失败 + */ + public boolean hset(String key, String item, Object value, long time) { + try { + redisTemplate.opsForHash().put(key, item, value); + if (time > 0) { + expire(key, time); + } + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 删除hash表中的值 + * + * @param key 键 不能为null + * @param item 项 可以使多个 不能为null + */ + public void hdel(String key, Object... item) { + redisTemplate.opsForHash().delete(key, item); + } + + /** + * 判断hash表中是否有该项的值 + * + * @param key 键 不能为null + * @param item 项 不能为null + * @return true 存在 false不存在 + */ + public boolean hHasKey(String key, String item) { + return redisTemplate.opsForHash().hasKey(key, item); + } + + /** + * hash递增 如果不存在,就会创建一个 并把新增后的值返回 + * + * @param key 键 + * @param item 项 + * @param by 要增加几(大于0) + * @return + */ + public double hincr(String key, String item, double by) { + return redisTemplate.opsForHash().increment(key, item, by); + } + + /** + * hash递减 + * + * @param key 键 + * @param item 项 + * @param by 要减少记(小于0) + * @return + */ + public double hdecr(String key, String item, double by) { + return redisTemplate.opsForHash().increment(key, item, -by); + } + + //============================set============================= + + /** + * 根据key获取Set中的所有值 + * + * @param key 键 + * @return + */ + public Set sGet(String key) { + try { + return redisTemplate.opsForSet().members(key); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + /** + * 根据value从一个set中查询,是否存在 + * + * @param key 键 + * @param value 值 + * @return true 存在 false不存在 + */ + public boolean sHasKey(String key, Object value) { + try { + return redisTemplate.opsForSet().isMember(key, value); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 将数据放入set缓存 + * + * @param key 键 + * @param values 值 可以是多个 + * @return 成功个数 + */ + public long sSet(String key, Object... values) { + try { + return redisTemplate.opsForSet().add(key, values); + } catch (Exception e) { + e.printStackTrace(); + return 0; + } + } + + /** + * 将set数据放入缓存 + * + * @param key 键 + * @param time 时间(秒) + * @param values 值 可以是多个 + * @return 成功个数 + */ + public long sSetAndTime(String key, long time, Object... values) { + try { + Long count = redisTemplate.opsForSet().add(key, values); + if (time > 0) { + expire(key, time); + } + return count; + } catch (Exception e) { + e.printStackTrace(); + return 0; + } + } + + /** + * 获取set缓存的长度 + * + * @param key 键 + * @return + */ + public long sGetSetSize(String key) { + try { + return redisTemplate.opsForSet().size(key); + } catch (Exception e) { + e.printStackTrace(); + return 0; + } + } + + /** + * 移除值为value的 + * + * @param key 键 + * @param values 值 可以是多个 + * @return 移除的个数 + */ + public long setRemove(String key, Object... values) { + try { + Long count = redisTemplate.opsForSet().remove(key, values); + return count; + } catch (Exception e) { + e.printStackTrace(); + return 0; + } + } + //===============================list================================= + + /** + * 获取list缓存的内容 + * + * @param key 键 + * @param start 开始 + * @param end 结束 0 到 -1代表所有值 + * @return + */ + public List lGet(String key, long start, long end) { + try { + return redisTemplate.opsForList().range(key, start, end); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + /** + * 获取list缓存的长度 + * + * @param key 键 + * @return + */ + public long lGetListSize(String key) { + try { + return redisTemplate.opsForList().size(key); + } catch (Exception e) { + e.printStackTrace(); + return 0; + } + } + + /** + * 通过索引 获取list中的值 + * + * @param key 键 + * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推 + * @return + */ + public Object lGetIndex(String key, long index) { + try { + return redisTemplate.opsForList().index(key, index); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + /** + * 将list放入缓存 + * + * @param key 键 + * @param value 值 + * @return + */ + public boolean lSet(String key, Object value) { + try { + redisTemplate.opsForList().rightPush(key, value); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 将list放入缓存 + * + * @param key 键 + * @param value 值 + * @param time 时间(秒) + * @return + */ + public boolean lSet(String key, Object value, long time) { + try { + redisTemplate.opsForList().rightPush(key, value); + if (time > 0) { + expire(key, time); + } + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 将list放入缓存 + * + * @param key 键 + * @param value 值 + * @return + */ + public boolean lSet(String key, List value) { + try { + redisTemplate.opsForList().rightPushAll(key, value); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 将list放入缓存 + * + * @param key 键 + * @param value 值 + * @param time 时间(秒) + * @return + */ + public boolean lSet(String key, List value, long time) { + try { + redisTemplate.opsForList().rightPushAll(key, value); + if (time > 0) { + expire(key, time); + } + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 根据索引修改list中的某条数据 + * + * @param key 键 + * @param index 索引 + * @param value 值 + * @return + */ + public boolean lUpdateIndex(String key, long index, Object value) { + try { + redisTemplate.opsForList().set(key, index, value); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 移除N个值为value + * + * @param key 键 + * @param count 移除多少个 + * @param value 值 + * @return 移除的个数 + */ + public long lRemove(String key, long count, Object value) { + try { + Long remove = redisTemplate.opsForList().remove(key, count, value); + return remove; + } catch (Exception e) { + e.printStackTrace(); + return 0; + } + } + + /** + * 模糊查询获取key值 + * + * @param pattern + * @return + */ + public Set keys(String pattern) { + return redisTemplate.keys(pattern); + } + + /** + * 使用Redis的消息队列 + * + * @param channel + * @param message 消息内容 + */ + public void convertAndSend(String channel, Object message) { + redisTemplate.convertAndSend(channel, message); + } + + + //=========BoundListOperations 用法 start============ + + /** + * 将数据添加到Redis的list中(从右边添加) + * + * @param listKey + * @param timeout 有效时间 + * @param unit 时间类型 + * @param values 待添加的数据 + */ + public void addToListRight(String listKey, long timeout, TimeUnit unit, Object... values) { + //绑定操作 + BoundListOperations boundValueOperations = redisTemplate.boundListOps(listKey); + //插入数据 + boundValueOperations.rightPushAll(values); + //设置过期时间 + boundValueOperations.expire(timeout, unit); + } + + /** + * 根据起始结束序号遍历Redis中的list + * + * @param listKey + * @param start 起始序号 + * @param end 结束序号 + * @return + */ + public List rangeList(String listKey, long start, long end) { + //绑定操作 + BoundListOperations boundValueOperations = redisTemplate.boundListOps(listKey); + //查询数据 + return boundValueOperations.range(start, end); + } + + /** + * 弹出右边的值 --- 并且移除这个值 + * + * @param listKey + */ + public Object rightPop(String listKey) { + //绑定操作 + BoundListOperations boundValueOperations = redisTemplate.boundListOps(listKey); + return boundValueOperations.rightPop(); + } + + //=========BoundListOperations 用法 End============ +} diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties index b27e20d..95adf70 100644 --- a/src/main/resources/application-dev.properties +++ b/src/main/resources/application-dev.properties @@ -5,11 +5,18 @@ spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/library?useSSL=false&useUnicode=yes&characterEncoding=UTF-8&allowPublicKeyRetrieval=true&serverTimezone=UTC # use your mysql username and pwd spring.datasource.username=root -spring.datasource.password=G132535yzy +spring.datasource.password= spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect spring.jpa.show-sql=true spring.jpa.properties.hibernate.format_sql=true +# redis +spring.data.redis.database=0 +spring.data.redis.host=127.0.0.1 +spring.data.redis.port=6379 +spring.data.redis.password=123456 +spring.data.redis.timeout=3000 + # 10 minutes wait time # (need to shorter than database's wait_timeout) spring.datasource.hikari.maxLifeTime=600000