用戶敏感信息脫敏展示:
@RequestParam 和 @PathVariable的區別
注解是用于從request中接收請求的,兩個都可以接收參數,關鍵點不同的是@RequestParam 是從request里面拿取值,而 @PathVariable 是從一個URI模板里面來填充。
@PathVariable:主要用于接收http://host:port/path/{參數值}數據。
@RequestParam:主要用于接收http://host:port/path?參數名=參數值數據,這里后面也可以不跟參數值。
BeanUtil.toBean()方法
在Hutool工具包中,BeanUtil類的toBean()方法用于將一個對象或Map轉換成指定類型的JavaBean對象。我們先創建了一個數據源對象product,然后,通過調用BeanUtil.toBean()方法,將數據源對象product轉換成目標類型 productVo的JavaBean對象vo。
需要注意的是,轉換過程中,BeanUtil.toBean()方法通過反射機制創建了目標類型的實例,并將數據源對象的屬性值復制到目標對象中。所以,需要保證數據源對象和目標類型的屬性名稱和類型保持一致,否則無法正確轉換屬性值。所以在創建ProductVo時,是繼承于Product的。另外,還需要在項目中引入Hutool的相關依賴才能使用BeanUtil類的方法。
PhoneDesensitizationSerializer:
package com.nageoffer.shortlink.admin.common.serialize;import cn.hutool.core.util.DesensitizedUtil;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;import java.io.IOException;/*** 手機號脫敏反序列化*/
public class PhoneDesensitizationSerializer extends JsonSerializer<String> {@Overridepublic void serialize(String phone, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {String phoneDesensitization = DesensitizedUtil.mobilePhone(phone);jsonGenerator.writeString(phoneDesensitization);}
}
UserRespDTO:
不知道為什么我都加了注解,也有文件,可是脫敏就是不生效,壓根沒走PhoneDesensitizationSerializer。
用戶注冊:
檢查用戶名是否存在:
- 海量用戶如果說查詢的用戶名存在或不存在,全部請求數據庫,會將數據庫直接打滿。
方法1:將數據庫已有的用戶名全部放到緩存里。
該方案問題:
- 是否要設置數據的有效期?只能設置為無無有效期,也就是永久數據。
- 如果是永久不過期數據,占用 Redis 內存太高。
方法2:使用布隆過濾器。
布隆過濾器是一種數據結構,用于快速判斷一個元素是否存在于一個集合中。具體來說,布隆過濾器包含一個位數組和一組哈希函數。位數組的初始值全部置為 0。在插入一個元素時,將該元素經過多個哈希函數映射到位數組上的多個位置,并將這些位置的值置為 1。
在查詢一個元素是否存在時,會將該元素經過多個哈希函數映射到位數組上的多個位置,如果所有位置的值都為 1,則認為元素存在;如果存在任一位置的值為 0,則認為元素不存在。
優點:
- 高效地判斷一個元素是否屬于一個大規模集合。
- 節省內存。
缺點:
- 可能存在一定的誤判。
布隆過濾器誤判理解
- 布隆過濾器要設置初始容量。容量設置越大,沖突幾率越低。
- 布隆過濾器會設置預期的誤判值。
誤判能否接受
布隆過濾器的誤判是否能夠接受?
答:可以容忍。為什么?因為用戶名不是特別重要的數據,如果說我設置用戶名為 aaa,系統返回我不可用,那我大可以在 aaa 的基礎上再加一個a,也就是 aaaa。
布隆過濾器的使用:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency><dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId>
</dependency>
spring:data:redis:host: 127.0.0.1port: 6379password: 123456
創建布隆過濾器實例:
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** 布隆過濾器配置*/
@Configuration
public class RBloomFilterConfiguration {/*** 防止用戶注冊查詢數據庫的布隆過濾器*/@Beanpublic RBloomFilter<String> userRegisterCachePenetrationBloomFilter(RedissonClient redissonClient) {RBloomFilter<String> cachePenetrationBloomFilter = redissonClient.getBloomFilter("xxx");cachePenetrationBloomFilter.tryInit(0, 0);return cachePenetrationBloomFilter;}
}
tryInit 有兩個核心參數:
- expectedInsertions:預估布隆過濾器存儲的元素長度。
- falseProbability:運行的誤判率。
錯誤率越低,位數組越長,布隆過濾器的內存占用越大。
錯誤率越低,散列 Hash 函數越多,計算耗時較長。
一個布隆過濾器占用大小的在線網站:Bloom Filter Calculator
使用布隆過濾器的兩種場景:
- 初始使用:注冊用戶時就向容器中新增數據,就不需要任務向容器存儲數據了。
- 使用過程中引入:讀取數據源將目標數據刷到布隆過濾器。
private final RBloomFilter<String> userRegisterCachePenetrationBloomFilter;/*** 查詢用戶名是否存在* @param username* @return 存在,true 不存在,false*/@Overridepublic Boolean hashUsername(String username) {return userRegisterCachePenetrationBloomFilter.contains(username);}
用戶注冊:
一定要記得寫枚舉的時候是用逗號分割。
redis使用:
由于很久沒用過redis了,所以我忘記了怎么啟動了。
可以參考這個文檔,為了防止下一次找不到redis文件了,記得一定要配置環境變量。
redis的啟動需要先打開redis-server.exe然后再用可視化工具連接即可。
如果redis沒有密碼,但是本地application.yml又配置了redis密碼,就會導致報錯:Factory method 'redisson' threw exception with message: Unable to connect to Redis server: 127.0.0.1/127.0.0.1:6379
Window下Redis的安裝和部署詳細圖文教程(Redis的安裝和可視化工具的使用)_redis安裝-CSDN博客
自動填充字段
自動填充字段 | MyBatis-Plus
難點:
由于在注冊時,將用戶記錄添加到數據庫時,同時將用戶名添加到布隆過濾器中。但是為了防止redis的主節點掛掉后,從節點變成主節點時,有些數據還沒復制,就會導致臟數據,就會可能導致用戶名重復,所以為了防止這個情況,我們需要將username設置為唯一索引,讓數據庫兜底。
如何防止惡意請求毫秒級觸發大量請求去一個未注冊的用戶名?
因為用戶名沒注冊,所以布隆過濾器不存在,代表著可以觸發注冊流程插入數據庫。但是如果惡意請求短時間海量請求,這些請求都會落到數據庫,造成數據庫訪問壓力。這里通過分布式鎖,鎖定用戶名進行串行執行,防止惡意請求利用未注冊用戶名將請求打到數據庫。
用redisson實現分布式鎖。
UserServiceImpl:
private final RedissonClient redissonClient;/*** 注冊用戶** @param requestParam*/@Overridepublic void register(UserRegisterReqDTO requestParam) {if(hashUsername(requestParam.getUsername())){throw new ClientException(UserErrorCodeEnum.USER_NAME_EXIST);}RLock lock = redissonClient.getLock(LOCK_USER_REGISTER_KEY+requestParam.getUsername());try{if(lock.tryLock()){int inserted=baseMapper.insert(BeanUtil.toBean(requestParam,UserDO.class));if(inserted<1){throw new ClientException(UserErrorCodeEnum.USER_SAVE_ERROR);}userRegisterCachePenetrationBloomFilter.add(requestParam.getUsername());return;}throw new ClientException(UserErrorCodeEnum.USER_NAME_EXIST);}finally {lock.unlock();}}
/*** 短鏈接后管 Redis 緩存常量類* 類描述: RedisCacheConstant**/
public class RedisCacheConstant {public static final String LOCK_USER_REGISTER_KEY="short-link:lock_user-register:";
}