当前位置:网站首页>Redis - redis in action - redis actual combat - actual combat Chapter 1 - SMS login function based on redis - redis + token shared session application - with code
Redis - redis in action - redis actual combat - actual combat Chapter 1 - SMS login function based on redis - redis + token shared session application - with code
2022-07-06 04:24:00 【Alascanfu】
Contents of this article
- Redis actual combat —— Actual combat
- SMS Login —— be based on Redis SMS login function
Redis actual combat —— Actual combat
Course is an introduction to
heima comment Redis —— project
1️⃣ SMS login —— Redis The share of session application
2️⃣ Merchant query cache —— Enterprise cache usage skills | Cache avalanche 、 Penetration and other problems are solved
3️⃣ Talent shop —— be based on List Like list | be based on SortedSet Like rankings
4️⃣ Coupons spike —— Redis The counter of | Lua Script Redis | Distributed lock | Redis Three message queues
5️⃣ Friends follow —— be based on Set Collective attention | Take off | Pay close attention to | Message push function
6️⃣ Nearby merchants —— Redis Of GeoHash Application
7️⃣ User check-in —— Redis Of BitMap Data statistics function
8️⃣ UV Statistics —— Redis Of HyperLogLog The statistical function of
SMS Login —— be based on Redis SMS login function
Import Dark horse comments project
Import backend project
Step one : First, import. SQL File into the database
The data structure of the table
1️⃣ tb_user: User table
2️⃣ tb_user_info : User details table
3️⃣ tb_shop : Commodity information table
4️⃣ tb_shop_type : Merchant type table
5️⃣ tb_blog : User journal table ( Da Ren shop diary )
6️⃣ tb_follow : User focus table
7️⃣ tb_voucher : Coupon list
8️⃣ tb_voucher_order : Order form for coupons
Be careful :MySQL Version adopted 5.7 And its version
Project framework
Step two : Import the written semi-finished product source code into IDEA And test
Step three : rewrite application.yaml file Rewrite it into its corresponding database
server:
port: 8081
spring:
application:
name: hmdp
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.56.1:3306/hmdp?useSSL=false&serverTimezone=UTC
username: root
password: root
redis:
host: 192.168.56.103
port: 6379
lettuce:
pool:
max-active: 10
max-idle: 10
min-idle: 1
time-between-eviction-runs: 10s
jackson:
default-property-inclusion: non_null # JSON Ignore non empty fields when processing
mybatis-plus:
type-aliases-package: com.hmdp.entity # Alias scanning package
logging:
level:
com.hmdp: debug
Test connection to database
Step four : start-up HmDianPingApplication And use the browser to access the test
http://localhost:8081/shop-type/list
Test import succeeded ~
Import front-end project
Step one : stay Nginx Open a CMD window , Enter the command :
start nginx.exe
Step two : open chrome browser , Right click on the blank page , Select Check , You can open the developer tools —— And turn on the phone mode
http://localhost:8080/
If it appears, it can be opened normally The front-end project But the corresponding pictures and data are not displayed , It means that your backend project is not enabled , The front end cannot refresh the rendering data to the front end by request , So make sure that the back-end application has been started .
As the picture shows, it's done ~
be based on Session To realize the login
Logical idea of sending SMS verification code
First of all, the user will submit a personal mobile phone number to request to send a verification code
The server will verify the mobile number :
- disqualification : Display the non-conforming prompt to the user , Prompt the user to re-enter the legal mobile phone number
- eligible : Generate the corresponding verification code .
Get the generated verification code and save it to Session among , Follow up Send verification code Business services
end
Step one : To the corresponding UserController Write the corresponding interface of layer 、 And the specific implementation of the corresponding interface service impl
/** * Send mobile phone verification code */
@PostMapping("code")
public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {
// TODO Send SMS verification code and save the verification code
return userService.sendCode(phone , session);
}
IUserService
Result sendCode(String phone, HttpSession session);
UserServiceImpl
/** * Function description * Get the request for obtaining the verification code when the user sends a mobile phone text message * @date 2022/7/4 * @author Alascanfu */
@Override
public Result sendCode(String phone, HttpSession session) {
// Judge the submitted by the current user Whether the mobile phone conforms to the correct Format
if (RegexUtils.isPhoneInvalid(phone)) {
// If format matching fails Then put back the error message to the front end
return Result.fail(" I'm sorry , There is a problem with the format of the mobile number you entered , Please try again !");
}
// If the format match is successful Then a random verification code is generated
String code = RandomUtil.randomNumbers(6);
// A good random verification code will be generated Save to session among
session.setAttribute("code",code);
// Send SMS verification code to the user
log.info(" Verification code sent successfully , code => {}",code);
// The verification code was sent successfully Return successful response
return Result.ok();
}
Step three : Start the program and test whether our corresponding verification code generation can be viewed in the background
SMS verification code login 、 Register logical ideas
First, the user will submit the verification code and mobile phone number received by himself in the form of request
The server verifies the verification code submitted by the user first :
- Mismatch : Then the user will be prompted that the verification code is entered incorrectly , Please try again
- matching : Query the user according to the mobile phone number
Search the database according to the mobile phone number
- There is no such user : It indicates that the current user is using it for the first time , Then automatically register an account for it , And add the account information to the database for persistence , Then save the user's data to Session Convenient to call data .
- There is this user : Save the user information to Session Then you can .
Step one : To UserController Write the corresponding login service interface in
/** * Login function * @param loginForm Login parameters , Including cell phone number 、 Verification Code ; Or cell phone number 、 password */
@PostMapping("/login")
public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
// TODO Implement login function
return userService.login(loginForm , session);
}
Step two : Write the corresponding service interface and the specific implementation of the service interface
Result login(LoginFormDTO loginForm, HttpSession session);
/** * Function description * The specific implementation class of login function Login parameters , Including cell phone number 、 Verification Code ; Or cell phone number 、 password * @date 2022/7/4 * @author Alascanfu */
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
// First get the user request sent Verification Code Progress and session Whether the corresponding verification code in matches
// If it doesn't match Then it is directly returned to the front end Explain the user verification code Incorrect
String cacheCode = (String) session.getAttribute(SystemConstants.SESSION_PHONE_CODE);
String cachePhone = (String) session.getAttribute(SystemConstants.SESSION_PHONE);
String code = loginForm.getCode();
String phone = loginForm.getPhone();
if (RegexUtils.isPhoneInvalid(loginForm.getPhone())){
return Result.fail(" I'm sorry , There is a problem with the format of the mobile number you entered , Please try again !");
}
if (!cachePhone.equals(phone)){
return Result.fail(" The verification code does not match the mobile phone number , Please try again !");
}
if (cacheCode == null || !cacheCode.equals(code)){
return Result.fail(" I'm sorry , The verification code you entered is wrong , Please try again !");
}
// If it matches, verify whether the mobile number entered by the user name is a previously registered
User user = userMapper.selectUserByPhone(phone);
if (user == null){
// On the contrary, if you have not registered , Then quickly help users register one The user account Fill in some default information
user = createUserWithPhone(phone);
}
// Will quickly register good users | Corresponding to the successful login user , Add to current session among
session.setAttribute(SystemConstants.SESSION_USER,user);
// Last login successful
return Result.ok();
}
private User createUserWithPhone(String phone) {
User user = new User();
user.setPhone(phone);
user.setNickName(SystemConstants.USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));
userMapper.insert(user);
return user;
}
Step three : Start the test
After successful login, it will automatically jump to the homepage
There is also corresponding data in the database ~
Check the logic of login status
- First of all, users' access requests to the website will carry cookie Information
- Then the server can provide Jsessionid see Server side Session Whether there is information about this user .
- without , User requests will be blocked , Return to the login screen , Prompt the user to log in again .
- If there is , Then the user's information Save to ThreadLocal This thread local variable field is convenient for subsequent operations . Then release the request .
Step one : Write interceptors 、 Used to verify Session Medium user Information
LoginInterceptor
/*** * @author: Alascanfu * @date : Created in 2022/7/4 20:21 * @description: LoginInterceptor * @modified By: Alascanfu **/
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// Through the first request obtain session
HttpSession session = request.getSession();
// And then through session Try to get user Information
User user = (User) session.getAttribute(SystemConstants.SESSION_USER);
if (user == null){
// If user non-existent Error message returned Intercept requests Set the returned status code as unauthorized
response.setStatus(401);
return false ;
}
// If there is user Save it to threadLocal among
UserDTO userDTO = userToUserDTO(user);
UserHolder.saveUser(userDTO);
return true;
}
/** User type Convert to UserDTO */
private UserDTO userToUserDTO(User user) {
UserDTO userDTO = new UserDTO();
userDTO.setId(user.getId());
userDTO.setIcon(user.getIcon());
userDTO.setNickName(user.getNickName());
return userDTO;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// When the request is complete Clear the corresponding threadLoacl Medium user Data is enough
UserHolder.removeUser();
}
}
Step two : establish WebMvcConfig class Customized interceptor configuration
/*** * @author: Alascanfu * @date : Created in 2022/7/4 20:36 * @description: Web MVC configuration * @modified By: Alascanfu **/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor ;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.excludePathPatterns(
"/shop/**",
"/voucher/**",
"/shop-type/**",
"/upload/**",
"/blog/hot",
"/user/code",
"/user/login"
);
}
}
Step three : rewrite UserController Medium /user/me Request interface
@GetMapping("/me")
public Result me(){
// TODO Get the currently logged in user and return
UserDTO user = UserHolder.getUser();
return Result.ok(user);
}
Step four : To test
Follow the steps of user login , View current user data
Clustered Session Sharing issues
Distributed session Sharing issues 4 Solutions and spring session Use
be based on Redis Share Session Sign in
You need to check the front-end code logic To understand which information in the request header the backend needs to obtain token Information data
Axios Front and back end asynchronous request Library The back-end staff is enough
// request Interceptor , Will the user token Put it in your head
let token = sessionStorage.getItem("token");
axios.interceptors.request.use(
config => {
if(token) config.headers['authorization'] = token
return config
},
error => {
console.log(error)
return Promise.reject(error)
}
)
It's clear that
let token = sessionStorage.getItem("token");
Get from browser memory token The number
axios.interceptors.request.use
This... Will be passed every time the request is sent axios Interceptor .
Put... In the request header authorization carry token Numerical information .
Step one : rewrite primary Session Store verification code => Redis Store verification code
1️⃣ Get the request data submitted by the user
i. Verify whether the mobile number format is correct
1. Incorrect => return error message
2. correct => Make the next logical judgment
2️⃣ If the mobile phone number format is correct, a random verification code will be generated
3️⃣ Will generate a good Verification Code Save to redis among
i. key => Business abbreviation : Unique attribute of business : + phone
ii.value => Verification Code
4️⃣ Return the request success information
/** * Function description * Get the request for obtaining the verification code when the user sends a mobile phone text message * @date 2022/7/4 * @author Alascanfu */
@Override
public Result sendCode(String phone, HttpSession session) {
// Judge the submitted by the current user Whether the mobile phone conforms to the correct Format
if (RegexUtils.isPhoneInvalid(phone)) {
// If format matching fails Then put back the error message to the front end
return Result.fail(" I'm sorry , There is a problem with the format of the mobile number you entered , Please try again !");
}
// If the format match is successful Then a random verification code is generated
String code = RandomUtil.randomNumbers(6);
// A good random verification code will be generated Save to Redis among
// k : login:code:18584100561 v : code expireTime 2 min
stringRedisTemplate.opsForValue()
.set(RedisConstants.LOGIN_CODE_KEY + phone ,
code , RedisConstants.LOGIN_CODE_TTL , TimeUnit.MINUTES);
// Send SMS verification code to the user
log.info(" Verification code sent successfully , code => {}",code);
// The verification code was sent successfully Return successful response
return Result.ok();
}
Step two : Rewrite the login specific logic
1️⃣ Get the information of the form submitted by the user LoginForm
i. Match whether the format of the currently submitted mobile number is correct ? “ Continue to the next logical ” : “ Return error message ”
ii. Submitted by the user Phone number And Redis Constant Splicing To obtain key Search for
1. Judge the currently obtained value Whether it is not empty ? “ Continue to the next logical ” : “ Return error message ”
2️⃣ adopt user Submitted mobile number To In the database Find corresponding data
i. If the user doesn't exist be Quickly create a user
ii. There is Execute the next logic
3️⃣ Random generation token As a login token And use it as key Save to redis among
i. Will get before User Object is converted to... That does not contain sensitive data UserDTO And then convert it to HashMap For storage
ii. Storage With Redis Constant + token by Key , With UserMap As data storage
4️⃣ Prevent a lot of k-v Occupy memory space for a long time So you need to set token The period of validity
5️⃣ return token
/** * Function description * The specific implementation class of login function Login parameters , Including cell phone number 、 Verification Code ; Or cell phone number 、 password * @date 2022/7/4 * @author Alascanfu */
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
// First get the user request sent Verification Code Progress and session Whether the corresponding verification code in matches
String code = loginForm.getCode();
String phone = loginForm.getPhone();
// TODO from Redis Get the verification code from And verify
String cacheCode = stringRedisTemplate.opsForValue()
.get(RedisConstants.LOGIN_CODE_KEY + phone);
if (RegexUtils.isPhoneInvalid(loginForm.getPhone())){
return Result.fail(" I'm sorry , There is a problem with the format of the mobile number you entered , Please try again !");
}
if (cacheCode == null || !cacheCode.equals(code)){
return Result.fail(" I'm sorry , The verification code you entered is wrong , Please try again !");
}
// If it matches, verify whether the mobile number entered by the user name is a previously registered
User user = userMapper.selectUserByPhone(phone);
if (user == null){
// On the contrary, if you have not registered , Then quickly help users register one The user account Fill in some default information
user = createUserWithPhone(phone);
}
// Will quickly register good users | Corresponding to the successful login user , Add to current session among
// session.setAttribute(SystemConstants.SESSION_USER,user);
// TODO Put user information Save to Redis in 1=> Random generation token
// 2=> take User Object to Hash For storage
// 3=> Store the data
// 4=> Set up token The period of validity
String token = UUID.randomUUID().toString(true);
UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
Map<String, Object> userMap = BeanUtil.beanToMap(userDTO);
String tokenKey = LOGIN_USER_KEY + token;
stringRedisTemplate.opsForHash().putAll(tokenKey,userMap);
stringRedisTemplate.expire(tokenKey,RedisConstants.LOGIN_USER_TTL ,TimeUnit.SECONDS);
// take token Back to the browser
return Result.ok(token);
}
Because we want to ensure that users will update again after each operation token Time of voucher , In order to avoid periodic culling token So we need to add the corresponding logic in the interceptor , Ensure that the user will update after each request token The lifetime of the certificate
Step three : Rewrite login verification logic
1️⃣ First of all, from the Get the... Carried by the user in the request header token Data and information
2️⃣ Get through token Information and Redis Constant Splicing from Redis Get the corresponding user data
3️⃣ Will be taken from Redis Get what you get Map Data to UserDTO Type data
4️⃣ take UserDTO data Add to the local variable of the current thread
5️⃣ Refresh Corresponding to the user token Certificate expiration time
6️⃣ release
/*** * @author: Alascanfu * @date : Created in 2022/7/4 20:21 * @description: LoginInterceptor * @modified By: Alascanfu **/
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Autowired
private StringRedisTemplate stringRedisTemplate ;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// Through the first request obtain session
// 1. Get the token
HttpSession session = request.getSession();
String token = request.getHeader("authorization");
if (StrUtil.isBlank(token)){
response.setStatus(401);
return false ;
}
// And then through session Try to get user Information
// 2. adopt token from Redis Get user information
// User user = (User) session.getAttribute(SystemConstants.SESSION_USER);
Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(RedisConstants.LOGIN_USER_KEY + token);
if (userMap.isEmpty()){
// If user non-existent Error message returned Intercept requests Set the returned status code as unauthorized
response.setStatus(401);
return false ;
}
// 3. take The result of the inquiry Hash The data type is converted to UserDTO object
// If there is user Save it to threadLocal among
UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap,new UserDTO(),false);
// 4. Save user information to ThreadLocal among
UserHolder.saveUser(userDTO);
// 7. Refresh token The period of validity
stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY + token , RedisConstants.LOGIN_USER_TTL, TimeUnit.SECONDS);
// 8. release
return true;
}
/** User type Convert to UserDTO */
private UserDTO userToUserDTO(User user) {
UserDTO userDTO = new UserDTO();
userDTO.setId(user.getId());
userDTO.setIcon(user.getIcon());
userDTO.setNickName(user.getNickName());
return userDTO;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// When the request is complete Clear the corresponding threadLoacl Medium user Data is enough
UserHolder.removeUser();
}
}
Step four : Do the corresponding test
After launching the program, we come to the user login interface , Login corresponding to the user , Then check the corresponding Redis Whether With data .
error :java.lang.Long cannot be cast to java.lang.String
2022-07-05 00:36:29.592 ERROR 24500 — [nio-8081-exec-5] com.hmdp.config.WebExceptionAdvice : java.lang.ClassCastException: java.lang.Long cannot be cast to java.lang.String
java.lang.ClassCastException: java.lang.Long cannot be cast to java.lang.String
at org.springframework.data.redis.serializer.StringRedisSerializer.serialize(StringRedisSerializer.java:36) ~[spring-data-redis-2.3.9.RELEASE.jar:2.3.9.RELEASE]
=> It's reported here Type conversion error , Mainly StringRedisSerializer This is caused by serializing the object .
=> Find the code error corresponding to the error according to the prompt
Solution
Rewrite the corresponding conversion logic
Map<String, Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>(),
CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fieldName,fieldValue)->{
return fieldValue.toString();
}));
To test
Optimization of login interceptor
Step one : Write a new global release interceptor RefreshTokenInterceptor
/*** * @author: Alascanfu * @date : Created in 2022/7/5 1:07 * @description: RefreshTokenInterceptor * @modified By: Alascanfu **/
@Component
public class RefreshTokenInterceptor implements HandlerInterceptor {
@Autowired
private StringRedisTemplate stringRedisTemplate ;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1. obtain token
String token = request.getHeader("authorization");
if (StrUtil.isBlank(token)){
return true ;
}
// 2. Query corresponding Redis user
String tokenKey = RedisConstants.LOGIN_USER_KEY + token ;
Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(tokenKey);
if (userMap.isEmpty()){
return true ;
}
UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
// 3. Save to ThreadLocal
UserHolder.saveUser(userDTO);
// 4. Refresh token The period of validity
stringRedisTemplate.expire(tokenKey,RedisConstants.LOGIN_USER_TTL, TimeUnit.SECONDS);
// 5. release
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
Step two : Rewrite optimization LoginInterceptor
/*** * @author: Alascanfu * @date : Created in 2022/7/4 20:21 * @description: LoginInterceptor * @modified By: Alascanfu **/
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// TODO Decide if you need to intercept Judge ThreadLocal Whether there are users
if (UserHolder.getUser() == null){
response.setStatus(401);
return false ;
}
// Let it go if there are users
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// When the request is complete Clear the corresponding threadLoacl Medium user Data is enough
UserHolder.removeUser();
}
}
Step three : rewrite WebMvc The configuration class configures the corresponding interceptor
/*** * @author: Alascanfu * @date : Created in 2022/7/4 20:36 * @description: Web MVC configuration * @modified By: Alascanfu **/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor ;
@Autowired
private RefreshTokenInterceptor refreshTokenInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(refreshTokenInterceptor)
.addPathPatterns("/**");
registry.addInterceptor(loginInterceptor)
.excludePathPatterns(
"/shop/**",
"/voucher/**",
"/shop-type/**",
"/upload/**",
"/blog/hot",
"/user/code",
"/user/login"
);
}
}
Step four : Start the application and open the browser to test
Refresh back to the home page to see if it has been refreshed token Voucher time
Knowledge points after class —— Interview questions to understand and master
Article, understand Cookie + Session,Redis + Token,JWT Differences among the three
Distributed session Sharing issues 4 Solutions and spring session Use
边栏推荐
- HotSpot VM
- npm命令--安装依赖包--用法/详解
- Several important classes in unity
- Brief tutorial for soft exam system architecture designer | general catalog
- CADD课程学习(8)-- 化合物库虚拟筛选(Virtual Screening)
- 图应用详解
- How does computer nail adjust sound
- Lora gateway Ethernet transmission
- User datagram protocol UDP
- Execution order of scripts bound to game objects
猜你喜欢
[tomato assistant installation]
View 工作流程
Introduction to hashtable
Implementation of knowledge consolidation source code 1: epoll implementation of TCP server
Solution of storage bar code management system in food industry
Certbot failed to update certificate solution
Fedora/rehl installation semanage
How do programmers teach their bosses to do things in one sentence? "I'm off duty first. You have to work harder."
Comprehensive ability evaluation system
How to solve the problem of slow downloading from foreign NPM official servers—— Teach you two ways to switch to Taobao NPM image server
随机推荐
Deep learning framework installation (tensorflow & pytorch & paddlepaddle)
View 工作流程
Basic knowledge of binary tree, BFC, DFS
One question per day (Mathematics)
CADD课程学习(7)-- 模拟靶点和小分子相互作用 (柔性对接 AutoDock)
Lombok原理和同时使⽤@Data和@Builder 的坑
10個 Istio 流量管理 最常用的例子,你知道幾個?
C. The Third Problem(找规律)
After learning classes and objects, I wrote a date class
[network] channel attention network and spatial attention network
MLAPI系列 - 04 - 网络变量和网络序列化【网络同步】
Easyrecovery靠谱不收费的数据恢复电脑软件
. Net interprocess communication
Jd.com 2: how to prevent oversold in the deduction process of commodity inventory?
Comprehensive ability evaluation system
When debugging after pycharm remote server is connected, trying to add breakpoint to file that does not exist: /data appears_ sda/d:/segmentation
The value of two date types is subtracted and converted to seconds
Leetcode32 longest valid bracket (dynamic programming difficult problem)
【leetcode】1189. Maximum number of "balloons"
Stable Huawei micro certification, stable Huawei cloud database service practice