主頁 > 後端開發 > SpringBoot + Vue + ElementUI 實作后臺管理系統模板 -- 后端篇(二): 整合 Redis(常用工具類、快取)、整合郵件發送功能

SpringBoot + Vue + ElementUI 實作后臺管理系統模板 -- 后端篇(二): 整合 Redis(常用工具類、快取)、整合郵件發送功能

2020-10-07 05:41:30 後端開發

(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--堆

下一篇:一個 Java 字串到底有多少個字符?

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more