(1) 相關博文地址:
SpringBoot + Vue + ElementUI 實作后臺管理系統模板 -- 前端篇(一):搭建基本環境:https://www.cnblogs.com/l-y-h/p/12930895.html SpringBoot + Vue + ElementUI 實作后臺管理系統模板 -- 前端篇(二):引入 element-ui 定義基本頁面顯示:https://www.cnblogs.com/l-y-h/p/12935300.html SpringBoot + Vue + ElementUI 實作后臺管理系統模板 -- 前端篇(三):引入 js-cookie、axios、mock 封裝請求處理以及回傳結果:https://www.cnblogs.com/l-y-h/p/12955001.html SpringBoot + Vue + ElementUI 實作后臺管理系統模板 -- 前端篇(四):引入 vuex 進行狀態管理、引入 vue-i18n 進行國際化管理:https://www.cnblogs.com/l-y-h/p/12963576.html SpringBoot + Vue + ElementUI 實作后臺管理系統模板 -- 前端篇(五):引入 vue-router 進行路由管理、模塊化封裝 axios 請求、使用 iframe 標簽嵌套頁面:https://www.cnblogs.com/l-y-h/p/12973364.html SpringBoot + Vue + ElementUI 實作后臺管理系統模板 -- 前端篇(六):使用 vue-router 進行動態加載選單:https://www.cnblogs.com/l-y-h/p/13052196.html SpringBoot + Vue + ElementUI 實作后臺管理系統模板 -- 后端篇(一): 搭建基本環境、整合 Swagger、MyBatisPlus、JSR303 以及國際化操作:https://www.cnblogs.com/l-y-h/p/13083375.html
(2)代碼地址:
https://github.com/lyh-man/admin-vue-template.git
一、SpringBoot 整合 Redis
1、簡單回顧一下 Redis
(1)特點:
基于 key-value 存盤,
支持多種資料結構:string(字串)、list(串列)、set(集合)、zset(sorted set 有序集合)、hash(哈希),
通過記憶體存盤、操作資料,支持持久化將資料存盤在硬碟中,
支持過期時間、事務,
參考:
https://www.cnblogs.com/l-y-h/p/12715723.html
(2)什么時候使用?
一般把經常查詢、不經常修改 且 不是特別重要的資料放到 redis 中作為快取(比如網站首頁的一些圖片、視頻等),
(3)Redis 與 Memcache 區別?
二者都是將資料快取在記憶體中,
Memcache 只能將資料快取在記憶體中,無法將資料寫入硬碟,即機器斷電、重啟使記憶體清空后,資料會丟失,適用于快取無需持久化的資料,
Redis 可以周期性的將資料存入磁盤(RDB)或者 將寫操作以追加的方式寫入檔案(AOF)的方式對資料進行持久化,
2、SpringBoot 整合 Redis 快取
(1)使用快取目的:
對于一些經常被查詢的資料,若每次都從資料庫中獲取,則會極大地消耗系統性能,將這些資料放在快取中,并設定過期時間,查詢時若快取中有值,則從快取中獲取,否則從資料庫中獲取,并將新值存入快取,這樣可以減輕資料庫的訪問頻率,從而提高系統性能,
(2)安裝 Redis
【docker 安裝 Redis 參考:】 https://www.cnblogs.com/l-y-h/p/12622730.html#_label6 【linux(CnetOs7) 安裝 Redis 參考:】 https://www.cnblogs.com/l-y-h/p/12656614.html#_label0_1
(3)添加依賴資訊
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- spring2.X集成redis所需common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>

(4)配置 Redis
SpringBoot 2.x 采用 lettuce 客戶端,而非 Jedis,
如下:host 為 redis 存在的 ip 地址(需修改),
【yml:】 spring: # Redis 配置 redis: # Redis 服務器地址 host: 121.26.184.41 # 連接埠號 port: 6379 # 資料庫索引(0 - 15) database: 0 # 連接超時時間(毫秒) timeout: 10000 # lettuce 引數 lettuce: pool: # 最大連接數(使用負值表示沒有限制) 默認為 8 max-active: 10 # 最大阻塞等待時間(使用負值表示沒有限制) 默認為 -1 ms max-wait: -1 # 最大空閑連接 默認為 8 max-idle: 5 # 最小空閑連接 默認為 0 min-idle: 0 【properties:】 # Redis 服務器地址 spring.redis.host=121.26.184.41 # 連接埠號 spring.redis.port=6379 # 資料庫(0 - 15) spring.redis.database= 0 # 超時時間(毫秒) spring.redis.timeout=600000 # lettuce 引數 # 最大連接數(使用負值表示沒有限制) 默認為 8 spring.redis.lettuce.pool.max-active=20 # 最大阻塞等待時間(使用負值表示沒有限制) 默認為 -1 spring.redis.lettuce.pool.max-wait=-1 # 最大空閑連接 默認為 8 spring.redis.lettuce.pool.max-idle=5 # 最小空閑連接 默認為 0 spring.redis.lettuce.pool.min-idle=0

(5)新增一個 Redis 配置類 RedisConfig.java
用于自定義配置、使用 Redis,
當然可以直接注入并使用 RedisTemplate,原始碼中給出的是 RedisTemplate<Object, Object>,使用起來有些許不便,

自定義 RedisConfig.java 配置類如下:
@EnableCaching 表示開啟快取功能,
package com.lyh.admin_template.back.common.config; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.StringRedisSerializer; import java.time.Duration; @EnableCaching @Configuration public class RedisConfig extends CachingConfigurerSupport { /** * 配置 redisTemplate */ @Bean(name = "redisTemplate") public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { // 實體化一個 redisTemplate RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); // 配置連接工廠 redisTemplate.setConnectionFactory(factory); // 設定 key 序列化方式 redisTemplate.setKeySerializer(new StringRedisSerializer()); // 設定 value 序列化方式 redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); // 設定 hash key 序列化方式 redisTemplate.setHashKeySerializer(new StringRedisSerializer()); // 設定 hash value 序列化方式 redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); return redisTemplate; } /** * 配置快取 */ @Bean public CacheManager cacheManager(RedisConnectionFactory factory) { // 定義快取 key 前綴,默認為 :: final String keyPrefix = ":"; // 快取物件配置 RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() // 設定快取默認超時時間:600 秒 .entryTtl(Duration.ofSeconds(600)) // 設定 key 序列化器 .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) // 設定 value 序列化器 .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())) // 如果該值為 null,則不快取 .computePrefixWith(cacheName -> cacheName + keyPrefix) .disableCachingNullValues(); // 根據 redis 快取配置生成 redis 快取管理器 RedisCacheManager redisCacheManager = RedisCacheManager.builder(factory) .cacheDefaults(config) .transactionAware() .build(); return redisCacheManager; } }

(6)使用注解標注即可,
常用注解:
【@Cacheable】 標注在方法上(一般標注在查詢操作的方法上),將方法的回傳結果進行快取,下次請求時, 若快取存在,則直接讀取快取資料, 若快取不存在,則執行方法,并將回傳結果再次保存在快取中, 常用屬性: value、cacheNames 表示快取命名空間,二選一,不能為空, keyGenerator 表示 key 生成規則, key 表示 key 名, 【@CachePut】 標注在方法上(一般標注在新增操作的方法上),將方法的回傳結果進行快取, 注: 其每次都會執行,不管快取中是否有值, 常用屬性: value、cacheNames 表示快取命名空間,二選一,不能為空, keyGenerator 表示 key 生成規則, key 表示 key 名, 【@CacheEvict】 標注在方法上(一般用于更新操作、洗掉操作的方法上),清空指定的快取, 常用屬性: value、cacheNames 表示快取命名空間,二選一,不能為空, keyGenerator 表示 key 生成規則, allEntries 默認為 false,若為 true 表示方法執行后清空快取, beforeInvocation 默認為 false,若為 true 表示方法執行前清空快取, key 表示 key 名,
在 TestController.java 中撰寫測驗代碼用于測驗上面三個注解的用法:
package com.lyh.admin_template.back.controller; import com.lyh.admin_template.back.common.utils.Result; import com.lyh.admin_template.back.service.UserService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * 用于測驗環境搭建各個功能是否成功 */ @RestController @RequestMapping("/test") @Api(tags = "測驗頁面") public class TestController { @Autowired private UserService userService; @Cacheable(value = "list", key = "'userList'") @ApiOperation(value = "測驗 Redis 快取注解 @Cacheable") @GetMapping("/testRedis/cacheable") public Result testRedisCacheable() { return Result.ok().data("item", userService.list()); } @CachePut(value = "list", key = "'userList2'") @ApiOperation(value = "測驗 Redis 快取注解 @CachePut") @GetMapping("/testRedis/cachePut") public Result testRedisCachePut() { return Result.ok().data("item", userService.list()); } @CacheEvict(value = "list", key = "'userList'") @ApiOperation(value = "測驗 Redis 快取注解 @CacheEvict") @GetMapping("/testRedis/cacheEvict") public Result testRedisCacheEvict() { return Result.ok().data("item", userService.list()); } }

使用 swagger.ui 簡單測驗一下:
首先,執行 testRedisCacheable、testRedisCachePut 方法,兩者獲取資料都是一樣的,且都會存于快取中,
直接修改資料庫資料,再次訪問,testRedisCacheable 不會獲取到資料庫最新的值(回傳快取中的值),但是 testRedisCachePut 會取到最新資料庫的值,
然后訪問 testRedisCacheEvict 方法,可以查看到 testRedisCacheable 的快取被洗掉,重新訪問 testRedisCacheable 可以獲取到最新資料庫的資料,

3、整一個 Redis 常用工具類 RedisUtil.java
(1)基本使用
RedisTemplate 中操作 Redis 如下:
HashOperations 通過 redisTemplate.opsForHash() 獲取,用于操作 hash
ValueOperations 通過 redisTemplate.opsForValue() 獲取,用于操作 string
ListOperations 通過 redisTemplate.opsForList() 獲取,用于操作 list
SetOperations 通過 redisTemplate.opsForSet() 獲取,用于操作 set
ZSetOperations 通過 redisTemplate.opsForZSet() 獲取,用于操作 zset
可以直接通過 方法獲取,也可以通過組態檔并使用 @Bean 標注方式交給 Spring 管理,
如下,在組態檔中通過 @Bean 標注,
package com.lyh.admin_template.back.common.config; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.*; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.StringRedisSerializer; import java.time.Duration; @EnableCaching @Configuration public class RedisConfig extends CachingConfigurerSupport { /** * 配置 redisTemplate */ @Bean(name = "redisTemplate") public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { // 實體化一個 redisTemplate RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); // 配置連接工廠 redisTemplate.setConnectionFactory(factory); // 設定 key 序列化方式 redisTemplate.setKeySerializer(new StringRedisSerializer()); // 設定 value 序列化方式 redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); // 設定 hash key 序列化方式 redisTemplate.setHashKeySerializer(new StringRedisSerializer()); // 設定 hash value 序列化方式 redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); return redisTemplate; } /** * 配置快取 */ @Bean public CacheManager cacheManager(RedisConnectionFactory factory) { // 定義快取 key 前綴,默認為 :: final String keyPrefix = ":"; // 快取物件配置 RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() // 設定快取默認超時時間:600 秒 .entryTtl(Duration.ofSeconds(600)) // 設定 key 序列化器 .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) // 設定 value 序列化器 .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())) // 如果該值為 null,則不快取 .computePrefixWith(cacheName -> cacheName + keyPrefix) .disableCachingNullValues(); // 根據 redis 快取配置生成 redis 快取管理器 RedisCacheManager redisCacheManager = RedisCacheManager.builder(factory) .cacheDefaults(config) .transactionAware() .build(); return redisCacheManager; } /** * 用于操作 hash */ @Bean public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) { return redisTemplate.opsForHash(); } /** * 用于操作 list */ @Bean public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) { return redisTemplate.opsForList(); } /** * 用于操作 set */ @Bean public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) { return redisTemplate.opsForSet(); } /** * 用于操作 zset */ @Bean public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) { return redisTemplate.opsForZSet(); } /** * 用于操作 String */ @Bean public ValueOperations<String, String> valueOperations(RedisTemplate<String, String> redisTemplate) { return redisTemplate.opsForValue(); } }

注:
上面對于 value 的處理,采用了 GenericJackson2JsonRedisSerializer 進行序列化、反序列化,
其余序列化方式可以參考鏈接(https://www.freesion.com/article/966738549/),
解決一個坑:
GenericJackson2JsonRedisSerializer 反序列化失敗時,首先檢查是否存在默認無參構造器(自定義構造器時,必須得帶上無參構造器(=_=) ),
(2)撰寫工具類 RedisUtil.java
如下代碼,基本覆寫了 Redis 各型別常用的方法,其余方法可以根據需要自行封裝,
package com.lyh.admin_template.back.common.utils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.*; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; /** * Redis 工具類 */ @Component public class RedisUtil { /** * 用于操作 redis */ @Autowired private RedisTemplate<String, Object> redisTemplate; /** * 用于操作 String */ @Autowired private ValueOperations<String, String> valueOperations; /** * 用于操作 hash */ @Autowired private HashOperations<String, String, Object> hashOperations; /** * 用于操作 list */ @Autowired private ListOperations<String, Object> listOperations; /** * 用于操作 set */ @Autowired private SetOperations<String, Object> setOperations; /** * 用于操作 zset */ @Autowired private ZSetOperations<String, Object> zSetOperations; /* ================================ common ================================== */ /** * 設定默認過期時間(一天) */ public final static long DEFAULT_EXPIRE = 60 * 60 * 24; /** * 不設定過期時間 */ public final static long NOT_EXPIRE = -1; /** * 設定 key 的過期時間(單位為 秒) */ public void expire(String key, long expire) { redisTemplate.expire(key, expire, TimeUnit.SECONDS); } /** * 根據 key 獲取過期時間 */ public Long getExpire(String key) { return StringUtils.isNotEmpty(key) ? redisTemplate.getExpire(key, TimeUnit.SECONDS) : null; } /** * 根據 pattern 回傳匹配的所有 key */ public Set<String> keys(String pattern) { return StringUtils.isNotEmpty(pattern) ? redisTemplate.keys(pattern) : null; } /** * 洗掉 一個 或 多個 key */ public void del(String... key) { if (key != null && key.length > 0) { if (key.length == 1) { redisTemplate.delete(key[0]); } else { redisTemplate.delete(CollectionUtils.arrayToList(key)); } } } /** * 判斷 key 是否存在 */ public Boolean hasKey(String key) { return StringUtils.isNotEmpty(key) ? redisTemplate.hasKey(key) : null; } /* ================================ common ================================== */ /* ================================ string ================================== */ /** * string set 操作,設定 key-value */ public void set(String key, String value) { set(key, value, DEFAULT_EXPIRE); } /** * string set 操作,設定過期時間 */ public void set(String key, String value, long expire) { valueOperations.set(key, value); if (expire != NOT_EXPIRE) { expire(key, expire); } } /** * string get 操作,獲取 value */ public String get(String key) { return StringUtils.isNotEmpty(key) ? valueOperations.get(key) : null; } /** * string incr 操作,加法 */ public Long incr(String key, long data) { if (data < 0) { throw new RuntimeException("請輸入正整數"); } return valueOperations.increment(key, data); } /** * string decr 操作,減法 */ public Long decr(String key, long data) { if (data < 0) { throw new RuntimeException("請輸入正整數"); } return valueOperations.decrement(key, data); } /* ================================ string ================================== */ /* ================================ hash ================================== */ /** * hash set 操作,設定 一個 field - value */ public void hset(String key, String field, Object value) { hset(key, field, value, DEFAULT_EXPIRE); } /** * hash set 操作,設定過期時間 */ public void hset(String key, String field, Object value, long expire) { hashOperations.put(key, field, value); if (expire != NOT_EXPIRE) { expire(key, expire); } } /** * hash set 操作,設定 多個 field - value */ public void hmset(String key, Map<String, Object> map) { hmset(key, map, DEFAULT_EXPIRE); } /** * hash set 操作,設定過期時間 * 此處過期時間是對 key 設定,而非 field(即 key 過期,所有的 field 均失效), */ public void hmset(String key, Map<String, Object> map, long expire) { hashOperations.putAll(key, map); if (expire != NOT_EXPIRE) { expire(key, expire); } } /** * hash keys 操作,獲取所有的 field */ public Set<String> hkeys(String key) { return StringUtils.isNotEmpty(key) ? hashOperations.keys(key) : null; } /** * hash exists 操作,判斷 keys 中是否存在某個 field */ public Boolean hHasKey(String key, String field) { if (StringUtils.isNotEmpty(key) && StringUtils.isNotEmpty(field)) { return hashOperations.hasKey(key, field); } return null; } /** * hash del 操作,洗掉一個 或 多個 field */ public void hdel(String key, String... field) { if (StringUtils.isNotEmpty(key) && field.length > 0) { hashOperations.delete(key, field); } } /** * hash get 操作,獲取一個 field 對應的 value 值 */ public Object hget(String key, String field) { if (StringUtils.isNotEmpty(key) && StringUtils.isNotEmpty(field)) { return hashOperations.get(key, field); } return null; } /** * hash get 操作,獲取所有的 value 值 */ public List<Object> hmget(String key) { return StringUtils.isNotEmpty(key) ? hashOperations.values(key) : null; } /** * hash get 操作,獲取所有的 field-value */ public Map<String, Object> hgetAll(String key) { return StringUtils.isNotEmpty(key) ? hashOperations.entries(key) : null; } /* ================================ hash ================================== */ /* ================================ list ================================== */ /** * list left push 操作,設定一個 value */ public void lpush(String key, Object value) { lpush(key, value, DEFAULT_EXPIRE); } /** * list left push 操作,設定過期時間 */ public void lpush(String key, Object value, long expire) { listOperations.leftPush(key, value); if (expire != NOT_EXPIRE) { expire(key, expire); } } /** * list left push 操作,設定多個 value */ public void lpush(String key, List<Object> value) { lpush(key, value, DEFAULT_EXPIRE); } /** * list left push 操作,設定過期時間 */ public void lpush(String key, List<Object> value, long expire) { listOperations.leftPushAll(key, value); if (expire != NOT_EXPIRE) { expire(key, expire); } } /** * list right push 操作,設定一個 value */ public void rpush(String key, Object value) { rpush(key, value, DEFAULT_EXPIRE); } /** * list right push 操作,設定過期時間 */ public void rpush(String key, Object value, long expire) { listOperations.rightPush(key, value); if (expire != NOT_EXPIRE) { expire(key, expire); } } /** * list right push 操作,設定多個 value */ public void rpush(String key, List<Object> value) { rpush(key, value, DEFAULT_EXPIRE); } /** * list right push 操作,設定過期時間 */ public void rpush(String key, List<Object> value, long expire) { listOperations.rightPushAll(key, value); if (expire != NOT_EXPIRE) { expire(key, expire); } } /** * list set 操作,根據 index 下標設定 value */ public void lsetIndex(String key, long index, Object value) { lsetIndex(key, index, value, DEFAULT_EXPIRE); } /** * list set 操作,設定過期時間 */ public void lsetIndex(String key, long index, Object value, long expire) { listOperations.set(key, index, value); if (expire != NOT_EXPIRE) { expire(key, expire); } } /** * list range get 操作,獲取指定范圍的 value */ public List<Object> lrange(String key, long start, long end) { return StringUtils.isNotEmpty(key) ? listOperations.range(key, start, end) : null; } /** * list index get 操作,根據指定下標回傳 value */ public Object lgetIndex(String key, long index) { return StringUtils.isNotEmpty(key) ? listOperations.index(key, index) : null; } /** * list len 操作, 獲取 list 長度 */ public Long llen(String key) { return StringUtils.isNotEmpty(key) ? listOperations.size(key) : null; } /** * list remove 操作,移除 list 中 指定數量的 value */ public Long lremove(String key, long count, Object value) { return StringUtils.isNotEmpty(key) ? listOperations.remove(key, count, value) : null; } /** * list trim 操作,截取指定范圍的 value,并作為新的 list */ public void ltrim(String key, long start, long end) { listOperations.trim(key, start, end); } /** * list left pop 操作,回傳頭部第一個元素 */ public Object lpop(String key) { return StringUtils.isNotEmpty(key) ? listOperations.leftPop(key) : null; } /** * list right pop 操作,回傳尾部第一個元素 */ public Object rpop(String key) { return StringUtils.isNotEmpty(key) ? listOperations.rightPop(key) : null; } /* ================================ list ================================== */ /* ================================ set ================================== */ /** * set set 操作,設定一個 或 多個 value */ public void sset(String key, Object... value) { sset(key, DEFAULT_EXPIRE, value); } /** * set set 操作,設定過期時間 */ public void sset(String key, long expire, Object... value) { setOperations.add(key, value); if (expire != NOT_EXPIRE) { expire(key, expire); } } /** * set smembers 操作,回傳所有 value */ public Set<Object> smembers(String key) { return StringUtils.isNotEmpty(key) ? setOperations.members(key) : null; } /** * set sisMember 操作,判斷是否存在 某個 value 值 */ public Boolean sisMember(String key, Object value) { return StringUtils.isNotEmpty(key) ? setOperations.isMember(key, value) : null; } /** * set scard 操作,回傳當前 value 個數, */ public Long slen(String key) { return StringUtils.isNotEmpty(key) ? setOperations.size(key) : null; } /** * set srem 操作,根據 value 值移除元素 */ public Long sremove(String key, Object... value) { if (StringUtils.isNotEmpty(key) && value.length > 0) { return setOperations.remove(key, value); } return null; } /** * set spop 操作,隨機移除一個元素 */ public Object spop(String key) { return StringUtils.isNotEmpty(key) ? setOperations.pop(key) : null; } /** * set spop 操作,隨機移除 指定個數的元素 */ public List<Object> spop(String key, long count) { return StringUtils.isNotEmpty(key) ? setOperations.pop(key, count) : null; } /** * set srandmember 操作,隨機回傳一個 元素(非移除) */ public Object srandomMember(String key) { return StringUtils.isNotEmpty(key) ? setOperations.randomMember(key) : null; } /** * set srandmember 操作,隨機回傳指定個數的 元素(非移除) */ public List<Object> srandomMember(String key, long count) { return StringUtils.isNotEmpty(key) ? setOperations.randomMembers(key, count) : null; } /* ================================ set ================================== */ /* ================================ zset ================================== */ /** * zset set 操作,設定一個 value */ public void zset(String key, Object value, double score) { zset(key, value, score, DEFAULT_EXPIRE); } /** * zset set 操作,設定 過期時間 */ public void zset(String key, Object value, double score, long expire) { zSetOperations.add(key, value, score); if (expire != NOT_EXPIRE) { expire(key, expire); } } /** * zset set 操作,設定多個 value */ public void zset(String key, Set<ZSetOperations.TypedTuple<Object>> value) { zset(key, value, DEFAULT_EXPIRE); } /** * zset set 操作,設定 過期時間 */ public void zset(String key, Set<ZSetOperations.TypedTuple<Object>> value, long expire) { zSetOperations.add(key, value); if (expire != NOT_EXPIRE) { expire(key, expire); } } /** * zset zrange 操作,回傳指定下標范圍的 value(升序) */ public Set<Object> zrange(String key, long start, long end) { return StringUtils.isNotEmpty(key) ? zSetOperations.range(key, start, end) : null; } /** * zset zrange 操作,回傳指定下標范圍的 value - score (升序) */ public Set<ZSetOperations.TypedTuple<Object>> zrangeWithScores(String key, long start, long end) { return StringUtils.isNotEmpty(key) ? zSetOperations.rangeWithScores(key, start, end) : null; } /** * zset zrange 操作,回傳指定 score 范圍的 value(升序) */ public Set<Object> zrangeByScore(String key, double min, double max) { return StringUtils.isNotEmpty(key) ? zSetOperations.rangeByScore(key, min, max) : null; } /** * zset zrevrange 操作,回傳指定下標范圍的 value(降序) */ public Set<Object> zreverseRange(String key, long start, long end) { return StringUtils.isNotEmpty(key) ? zSetOperations.reverseRange(key, start, end) : null; } /** * zset zrevrange 操作,回傳指定 score 范圍的 value(降序) */ public Set<Object> zreverseRangeByScore(String key, double min, double max) { return StringUtils.isNotEmpty(key) ? zSetOperations.reverseRangeByScore(key, min, max) : null; } /** * zset zrevrange 操作,回傳指定下標范圍的 value - score (升序) */ public Set<ZSetOperations.TypedTuple<Object>> zreverseRangeWithScores (String key, long start, long end) { return StringUtils.isNotEmpty(key) ? zSetOperations.reverseRangeWithScores(key, start, end) : null; } /** * zset zcard 操作,回傳 value 個數 */ public Long zlen(String key) { return StringUtils.isNotEmpty(key) ? zSetOperations.size(key) : null; } /** * zset zcount 操作,回傳指定 score 范圍內的 value 個數, */ public Long zlenByScore(String key, long min, long max) { return StringUtils.isNotEmpty(key) ? zSetOperations.count(key, min, max) : null; } /** * zset zscore 操作,回傳指定 value 的 score 值 */ public Double zscore(String key, Object value) { return StringUtils.isNotEmpty(key) ? zSetOperations.score(key, value) : null; } /** * zset zrem 操作,根據 value 移除一個 或 多個 value */ public Long zremove(String key, Object... value) { return StringUtils.isNotEmpty(key) ? zSetOperations.remove(key, value) : null; } /** * zset zremrangebyscore 操作,按照指定 score 范圍移除 value */ public Long zremoveByScore(String key, long min, long max) { return StringUtils.isNotEmpty(key) ? zSetOperations.removeRangeByScore(key, min, max) : null; } /** * zset zremrangebyrank 操作,按照排序下標范圍 移除 value, */ public Long zremoveRange(String key, long start, long end) { return StringUtils.isNotEmpty(key) ? zSetOperations.removeRange(key, start, end) : null; } /** * zset zrank 操作,回傳升序序列中 value 的排名 */ public Long zrank(String key, Object value) { return StringUtils.isNotEmpty(key) ? zSetOperations.rank(key, value) : null; } /** * zset zrevrank 操作,回傳降序序列中 value 的排名 */ public Long zreverseRank(String key, Object value) { return StringUtils.isNotEmpty(key) ? zSetOperations.reverseRank(key, value) : null; } /* ================================ zset ================================== */ }
(3)簡單測驗一下:
撰寫一個測驗類,簡單測驗一下,
package com.lyh.admin_template.back; import com.lyh.admin_template.back.common.utils.RedisUtil; import lombok.Data; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.core.DefaultTypedTuple; import org.springframework.data.redis.core.ZSetOperations; import org.springframework.util.CollectionUtils; import java.util.*; @SpringBootTest public class TestRedis { @Autowired private RedisUtil redisUtil; /** * 測驗 Redis common 操作 */ @Test void testCommon() { redisUtil.set("hello", "10", 100); System.out.println("所有的 key: " + redisUtil.keys("*")); System.out.println("key 中是否存在 hello: " + redisUtil.hasKey("hello")); System.out.println("獲取 hello 的過期時間: " + redisUtil.getExpire("hello")); redisUtil.del("hello"); System.out.println("獲取 hello 的過期時間: " + redisUtil.getExpire("hello")); } /** * 測驗 Redis string 操作 */ @Test void testString() { System.out.println("所有的 key: " + redisUtil.keys("*")); redisUtil.set("hello", "10", 100); System.out.println(redisUtil.get("hello")); System.out.println(redisUtil.incr("hello",100)); System.out.println(redisUtil.get("hello")); } /** * 測驗 Redis hash 操作 */ @Test void testHash() { List<Student> lists = new ArrayList<>(); lists.add(new Student("tom")); lists.add(new Student("jarry")); Teacher teacher = new Teacher(); teacher.setName("teacher"); teacher.setStudents(lists); Map<String, Object> map = new HashMap<>(); map.put("tom", 12); map.put("jarry", 24); redisUtil.hset("hash", "hello", teacher); redisUtil.hmset("hash", map, 100); System.out.println(redisUtil.hkeys("hash")); System.out.println(redisUtil.hHasKey("hash", "hello")); System.out.println(redisUtil.hget("hash", "hello")); System.out.println(redisUtil.hgetAll("hash")); System.out.println(redisUtil.hmget("hash")); System.out.println(redisUtil.getExpire("hash")); redisUtil.del("hash", "hello", "hello2"); System.out.println(redisUtil.hkeys("hash")); } /** * 測驗 Redis list 操作 */ @Test void testList() { redisUtil.lpush("list", 1); redisUtil.rpush("list", 2); redisUtil.lpush("list", CollectionUtils.arrayToList(new int[]{3, 4, 5})); redisUtil.rpush("list", CollectionUtils.arrayToList(new int[]{6, 7, 8})); System.out.println(redisUtil.llen("list")); System.out.println(redisUtil.lrange("list", 0, -1)); redisUtil.lsetIndex("list", 2, 10); System.out.println(redisUtil.lgetIndex("list", 2)); System.out.println(redisUtil.lrange("list", 0, -1)); System.out.println(redisUtil.lremove("list", 10, 2)); System.out.println(redisUtil.lrange("list", 0, -1)); redisUtil.ltrim("list", 0, 3); System.out.println(redisUtil.lrange("list", 0, -1)); System.out.println(redisUtil.lpop("list")); System.out.println(redisUtil.rpop("list")); System.out.println(redisUtil.lrange("list", 0, -1)); redisUtil.del("list"); } /** * 測驗 Redis set 操作 */ @Test void testSet() { redisUtil.sset("set", "1"); redisUtil.sset("set", "1", "2", "3"); System.out.println(redisUtil.smembers("set")); System.out.println(redisUtil.sisMember("set", "2")); System.out.println(redisUtil.slen("set")); System.out.println(redisUtil.srandomMember("set", 10)); System.out.println(redisUtil.smembers("set")); System.out.println(redisUtil.spop("set")); System.out.println(redisUtil.smembers("set")); redisUtil.del("set"); } /** * 測驗 Redis zset 操作 */ @Test void testZset() { redisUtil.zset("zset", "20", 1); redisUtil.zset("zset", "10", 1); redisUtil.zset("zset", "30", 2); System.out.println(redisUtil.zrange("zset", 0, -1)); System.out.println(redisUtil.zrangeByScore("zset", 0, 1)); System.out.println(redisUtil.zreverseRange("zset", 0, -1)); System.out.println(redisUtil.zreverseRangeByScore("zset", 0, 1)); System.out.println(redisUtil.zlen("zset")); System.out.println(redisUtil.zlenByScore("zset", 0, 1)); System.out.println(redisUtil.zscore("zset", "20")); // System.out.println(redisUtil.zremove("zset", "20", "30", "40")); // System.out.println(redisUtil.zremoveRange("zset", 1, 4)); System.out.println(redisUtil.zremoveByScore("zset", 2, 4)); System.out.println(redisUtil.zrange("zset", 0, -1)); redisUtil.zset("zset", new Student("tom"), 10); redisUtil.zset("zset", new Student("jarry"), 20); System.out.println(redisUtil.zrange("zset", 0, -1)); System.out.println(redisUtil.zrank("zset", new Student("tom"))); System.out.println(redisUtil.zreverseRange("zset", 0, -1)); System.out.println(redisUtil.zreverseRank("zset", new Student("tom"))); Set<ZSetOperations.TypedTuple<Object>> set = new HashSet<>(); set.add(new DefaultTypedTuple<Object>("20", 1.0)); set.add(new DefaultTypedTuple<Object>("10", 1.0)); set.add(new DefaultTypedTuple<Object>("30", 2.0)); redisUtil.zset("zset", set); redisUtil.zrangeWithScores("zset", 0, -1).forEach((item) -> { System.out.println(item.getValue() + "===" + item.getScore()); }); redisUtil.zreverseRangeWithScores("zset", 0, -1).forEach((item) -> { System.out.println(item.getValue() + "===" + item.getScore()); }); redisUtil.del("zset"); } } @Data class Teacher { private List<Student> students; private String name; } @Data class Student { private String name; Student() { } Student(String name) { this.name = name; } }

簡單測驗一下 hash 操作,
主要看看序列化、反序列化問題,

二、SpringBoot 發送郵件
1、簡單了解一下基本概念
(1)常見協議:
想要在計算機之間發送郵件,需要各種協議來支撐,比如:SMTP、IMAP、POP3 等,
【來源于網路:】 【SMTP:】 為 Simple Mail Transfer Protocol 簡寫,即簡單郵件傳輸協議, 是一組從源地址到目的地地址傳輸郵件的規范, 需要使用 賬號名、密碼 才能登陸 SMTP 服務器,從而減少用戶收到垃圾郵件的機會, 主要用于郵件客戶端 與 郵件服務器之間郵件的發送、接收, 【IMAP:】 為 Internet Mail Access Protocol 簡寫,即互聯網郵件訪問協議, 主要用于郵件客戶端 從 郵件服務器上獲取郵件資訊、下載郵件, 【POP 3:】 為 Post Office Protocol 3 簡寫,即郵局協議 3, 是一個離線協議標準,即 郵件發送到服務器后,當郵件客戶端訪問郵件服務器時,會下載所有的未讀的郵件, 主要也用于 下載郵件, 【IMAP 與 POP 3 的區別:】 IMAP 在郵件客戶端的操作 會 影響到郵件服務器, POP 3 在郵件客戶端的操作 不會 影響到郵件服務器,
(2)開啟郵件服務(此處使用 網易郵箱),
Step1:
登錄 網易 163 郵箱(注冊、登錄一個郵箱作為服務器),
【網址:】 https://email.163.com/

Step2:
選擇設定,并點擊 POP3/SMTP/IMAP,

Step3:
點擊開啟 IMAP/SMTP服務、POP3/SMTP服務,


授權碼是用于登錄第三方郵件客戶端的專用密碼,
【IMAP/SMTP服務 授權碼:】
KFYRVDQAUXLNBSUO
(3)簡單了解一下 JavaMailSender 和 JavaMailSenderImpl
JavaMailSender 是 Spring 官方提供的集成郵件服務的一個介面,
JavaMailSenderImpl 是 JavaMailSender 的一個實作類,
通過 @Autowired 注入并使用 JavaMailSenderImpl 的 send 方法即可發送郵件,
查看 MailSenderPropertiesConfiguration.java 配置類截圖如下:

對于簡單的郵件,可以使用 SimpleMailMessage 封裝相關資訊,
對于復雜的郵件(比如存在附件),可以使用 MimeMessage 封裝相關資訊(通過 MimeMessageHelper 進一步操作相關資訊),
2、SpringBoot 發送郵件
(1)添加依賴
由于可能涉及到 json 與 string 之間的相互轉換,可以引入 Gson 依賴,
<!--Gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
<!-- mail -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>

(2)配置郵箱資訊
spring: # mail 配置 mail: # SMTP 服務器地址 host: smtp.163.com # 郵件服務器賬號 username: m_17730125031@163.com # 授權碼 password: KFYRVDQAUXLNBSUO # 配置埠號(默認使用 25,若專案發布到云服務器,需要開放相應埠 465,需要配置相關 ssl 協議) port: 465 # 編碼字符集采用 UTF-8 default-encoding: UTF-8 # 配置 ssl 協議(埠為 25 時,可以不用配置) properties: mail: smtp: ssl: enable: true socketFactory: port: 465 class: javax.net.ssl.SSLSocketFactory # 檔案上傳大小配置(由于附件的存在,可以視專案情況修改) servlet: multipart: # 限制單個檔案大小 max-file-size: 10MB # 限制單次請求總檔案大小 max-request-size: 50MB

(3)撰寫一個 vo 物件(MailVo.java),用于保存郵件相關資訊,
package com.lyh.admin_template.back.vo; import lombok.Data; import org.springframework.web.multipart.MultipartFile; @Data public class MailVo { /** * 郵件發送人 */ private String from; /** * 郵件接收人 */ private String[] to; /** * 郵件抄送 */ private String[] cc; /** * 郵件密送 */ private String[] bcc; /** * 郵件主題 */ private String subject; /** * 郵件內容 */ private String text; /** * 郵件附件 */ private MultipartFile[] files; /** * 郵件附件在服務器存盤的地址 */ private String[] fileUrls; }

(4)撰寫一個郵件發送工具類(MailUtil.java),每次發郵件呼叫即可,
當然,對郵件資料還是需要做必要的檢查,比如使用 正則運算式檢查郵箱格式,
正則運算式在線生成器:https://www.sojson.com/regex/generate
package com.lyh.admin_template.back.common.utils; import com.lyh.admin_template.back.vo.MailVo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; import javax.mail.MessagingException; import java.util.Date; /** * 郵件發送 工具類 */ @Component public class MailUtil { /** * 用于操作郵件 */ @Autowired private JavaMailSender javaMailSender; /** * 發送郵件 */ public void sendMail(MailVo mailVo) { // 檢查郵件地址是否正確 if (checkMail(mailVo)) { // 發送郵件 sendMailReal(mailVo); } else { throw new RuntimeException("郵件地址例外"); } } /** * 郵件必須項檢查 */ private boolean checkMail(MailVo mailVo) { return checkAddress(mailVo.getFrom()) && checkAddress(mailVo.getTo()) && checkAddress(mailVo.getCc()) && checkAddress(mailVo.getBcc()); } /** * 檢查郵箱地址是否正確 */ private boolean checkAddress(String... address) { if (address.length == 0 || address == null) { return true; } String regex = "\\w[-\\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\\.)+[A-Za-z]{2,14}"; for (String item : address) { if (!item.matches(regex)) { return false; } } return true; } /** * 發送郵件真實操作 */ private void sendMailReal(MailVo mailVo) { // 構造一個郵件助手 MimeMessageHelper mimeMessageHelper = null; try { // 傳入 MimeMessage,并對其進行一系列操作,true 表示支持復雜郵件(支持附件等) mimeMessageHelper = new MimeMessageHelper(javaMailSender.createMimeMessage(), true); // 設定郵件發送人 mimeMessageHelper.setFrom(mailVo.getFrom()); // 設定郵件接收人 mimeMessageHelper.setTo(mailVo.getTo()); // 設定郵件抄送人 mimeMessageHelper.setCc(mailVo.getCc()); // 設定郵件密送人 mimeMessageHelper.setBcc(mailVo.getBcc()); // 設定郵件主題 mimeMessageHelper.setSubject(mailVo.getSubject()); // 設定郵件內容 mimeMessageHelper.setText(mailVo.getText()); // 設定郵件日期 mimeMessageHelper.setSentDate(new Date()); // 設定附件 for (MultipartFile file : mailVo.getFiles()) { mimeMessageHelper.addAttachment(file.getOriginalFilename(), file); } // 發送郵件 javaMailSender.send(mimeMessageHelper.getMimeMessage()); } catch (MessagingException e) { throw new RuntimeException("郵件發送失敗"); } } }

(5)撰寫測驗類 TestMailController.java,對郵件發送功能進行測驗,
碰到的問題:
由于存在附件,后臺介面中可以使用 MultipartFile 型別去接收檔案,但是若想同時接收其他引數 ,不能直接使用 @RequestBody 接收引數轉為相應物體類,其屬于兩種流資訊,@RequestBody 屬于 application/json,MultipartFile 屬于 multipart/form-data,兩者同時使用,決議時就會出現問題(有能完美解決這個問題的,還望不吝賜教 (+_+)),
我的解決方案:
第一種形式,引數使用 HttpServletRequest 接收,然后自己手動去決議,
第二種方式,使用 @RequestParam 注解去接收,若引數過多,可以使用 String 去保存 前端發送的 JSON 資料,然后在后臺 手動將 String 資料轉為相應的物體類去處理(可使用 Gson),
第三種方式,將附件單獨處理(做一個檔案上傳介面,回傳附件在服務器的地址),發送郵件時,根據服務器地址獲取附件資訊,然后發送,這樣做就可以使用 @RequestBody 注解了,因為傳遞的是附件 URL 地址(String)而非檔案,
【引數接收例外:】 public Result testFiles(@RequestBody MailVo test, MultipartFile[] multipartFiles) 【引數接收正常:(方式一)】 @PostMapping("/send") public Result send(HttpServletRequest request) { MultipartHttpServletRequest params = (MultipartHttpServletRequest) request; List<MultipartFile> files = params.getFiles("files"); MailVo mailVoReal = GsonUtil.fromJson(params.getParameter("mailVo"), MailVo.class); mailVoReal.setFiles(files.toArray(new MultipartFile[]{})); mailUtil.sendMail(mailVoReal); return Result.ok(); } 【引數接收正常:(方式二)】 @PostMapping("/send2") public Result send2(@RequestParam String mailVo, MultipartFile[] files) { MailVo mailVoReal = GsonUtil.fromJson(mailVo, MailVo.class); mailVoReal.setFiles(files); mailUtil.sendMail(mailVoReal); return Result.ok(); }
涉及到 Gson 對資料進行轉換,為了方便使用,將其抽成一個工具類 GsonUtil.java,
【依賴:】 <!--Gson --> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.8.6</version> </dependency> 【工具類:】 package com.lyh.admin_template.back.common.utils; import com.google.gson.Gson; /** * Gson 工具類,用于 Object 與 Json 字串形式互轉 */ public class GsonUtil { private final static Gson GSON = new Gson(); /** * Object 轉 String 資料(JSON 字串) */ public static String toJson(Object object) { if (object instanceof Integer || object instanceof Short || object instanceof Byte || object instanceof Long || object instanceof Character || object instanceof Boolean || object instanceof Double || object instanceof String || object instanceof Float) { return String.valueOf(object); } return GSON.toJson(object); } /** * string(Json 字串) 轉 Object, */ public static <T> T fromJson(String json, Class<T> tClass) { return GSON.fromJson(json, tClass); } }

完整 TestMailController.java 如下:
package com.lyh.admin_template.back.controller.test; import com.lyh.admin_template.back.common.utils.GsonUtil; import com.lyh.admin_template.back.common.utils.MailUtil; import com.lyh.admin_template.back.common.utils.Result; import com.lyh.admin_template.back.vo.MailVo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartHttpServletRequest; import javax.servlet.http.HttpServletRequest; import java.util.List; /** * 測驗郵件發送功能 */ @RestController @RequestMapping("/test/mail") public class TestMailController { @Autowired private MailUtil mailUtil; /** * 獲取郵件方式一,使用 HttpServletRequest 獲取,并手動決議資料 */ @PostMapping("/send") public Result send(HttpServletRequest request) { MultipartHttpServletRequest params = (MultipartHttpServletRequest) request; List<MultipartFile> files = params.getFiles("files"); MailVo mailVoReal = GsonUtil.fromJson(params.getParameter("mailVo"), MailVo.class); mailVoReal.setFiles(files.toArray(new MultipartFile[]{})); mailUtil.sendMail(mailVoReal); return Result.ok(); } /** * 獲取郵件方式二,使用 @RequestParam 獲取 json 字串(使用 Gson 手動轉換為 物件) */ @PostMapping("/send2") public Result send2(@RequestParam String mailVo, MultipartFile[] files) { MailVo mailVoReal = GsonUtil.fromJson(mailVo, MailVo.class); mailVoReal.setFiles(files); mailUtil.sendMail(mailVoReal); return Result.ok(); } }

(6)測驗
由于 Swagger 傳遞多個檔案時,得到的值為 null(不知道怎么解決),
所以此處使用 postman 進行測驗,
測驗需要的 json 資料如下:
【用于測驗方式一:】 { "bcc": [], "cc": [], "from": "[email protected]", "subject": "測驗 1", "text": "測驗郵件發送 11111111111111", "to": [ "[email protected]" ] } 【用于測驗方式二:】 { "bcc": [ "[email protected]" ], "cc": [ "[email protected]", m_17730125031@163.com ], "from": "[email protected]", "subject": "測驗 2", "text": "測驗郵件發送 22222222222222222", "to": [ "[email protected]", m_17730125031@163.com ] }
郵件發送的程序有點慢(暫時不知道怎么解決 (=_=)),但是確實是可以發送的,

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/160766.html
標籤:Java
上一篇:JVM--堆
