Cet article présente principalement les connaissances pertinentes de Redis, et présente principalement l'application de session partagée de Redis pour réaliser une connexion par SMS. L'article la présente en détail à travers un exemple de code, qui a une certaine valeur de référence et d'apprentissage pour les études ou le travail de chacun. . J'espère que cela sera utile à tout le monde.
Apprentissage recommandé : Tutoriel vidéo Redis
1. Implémenter la connexion par SMS en fonction de la session
1.1 Organigramme de connexion par SMS
1.2 Implémenter l'envoi du code de vérification par SMS
Instructions de demande frontale :
|
Instructions |
Méthode de demande |
POST |
Chemin de la demande |
/utilisateur/code |
Paramètres de la demande |
téléphone (numéro de téléphone) |
Valeur de retour | "Aucun" | POST
Chemin de la demande
/user/login
Paramètres de la demande
téléphone (numéro de téléphone) ; code (code de vérification) |
| Valeur de retour
Aucun |
|
Implémentation de l'interface backend :@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Override
public Result sendCode(String phone, HttpSession session) {
// 1. 校验手机号
if(RegexUtils.isPhoneInvalid(phone)){
// 2. 如果不符合,返回错误信息
return Result.fail("手机号格式错误!");
}
// 3. 符合,生成验证码(设置生成6位)
String code = RandomUtil.randomNumbers(6);
// 4. 保存验证码到 session
session.setAttribute("code", code);
// 5. 发送验证码(这里并未实现,通过日志记录)
log.debug("发送短信验证码成功,验证码:{}", code);
// 返回 ok
return Result.ok();
}
}
Copier après la connexion
1.4 pour implémenter la vérification de connexion Interceptor | Implémentation de l'intercepteur de vérification de connexion : | @Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
// 1. 校验手机号
String phone = loginForm.getPhone();
if(RegexUtils.isPhoneInvalid(phone)){
// 不一致,返回错误信息
return Result.fail("手机号格式错误!");
}
// 2. 校验验证码
String cacheCode = (String) session.getAttribute("code");
String code = loginForm.getCode();
if(cacheCode == null || !cacheCode.equals(cacheCode)){
// 不一致,返回错误信息
return Result.fail("验证码错误!");
}
// 4. 一致,根据手机号查询用户(这里使用的 mybatis-plus)
User user = query().eq("phone", phone).one();
// 5. 判断用户是否存在
if(user == null){
// 6. 不存在,创建新用户并保存
user = createUserWithPhone(phone);
}
// 7. 保存用户信息到 session 中(通过 BeanUtil.copyProperties 方法将 user 中的信息过滤到 UserDTO 上,即用来隐藏部分信息)
session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));
return Result.ok();
}
private User createUserWithPhone(String phone) {
// 1. 创建用户
User user = new User();
user.setPhone(phone);
user.setNickName("user_" + RandomUtil.randomString(10));
// 2. 保存用户(这里使用 mybatis-plus)
save(user);
return user;
}
}
Copier après la connexion
Copier après la connexion
Implémentation de la classe UserHolder : Cette classe définit un ThreadLocalpublic class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1. 获取 session
HttpSession session = request.getSession();
// 2. 获取 session 中的用户
UserDTO user = (UserDTO) session.getAttribute("user");
// 3. 判断用户是否存在
if(user == null){
// 4. 不存在,拦截,返回 401 未授权
response.setStatus(401);
return false;
}
// 5. 存在,保存用户信息到 ThreadLocal
UserHolder.saveUser(user);
// 6. 放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 移除用户,避免内存泄露
UserHolder.removeUser();
}
}
Copier après la connexion
Copier après la connexion
Intercepteur de configuration statique : | public class UserHolder {
private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();
public static void saveUser(UserDTO user){
tl.set(user);
}
public static UserDTO getUser(){
return tl.get();
}
public static void removeUser(){
tl.remove();
}
}
Copier après la connexion
Description de la demande frontale : |
| | Description
Méthode de requête
POST
Chemin de la requête
/user/me
Paramètres de la requêteAucun | | Valeur de retour
Aucun | |
Implémentation de l'interface backend : @Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.excludePathPatterns(
"/user/login",
"/user/code"
);
}
}
Copier après la connexion
2. Problème de partage de session de cluster | Problème de partage de session : |
Plusieurs Tomcats ne partagent pas l'espace de stockage de session Lorsque la demande est basculée vers différents services Tomcat, cela entraînera une perte de données. Les alternatives de session doivent remplir les conditions suivantes : | | Partage de données (différents Tomcats peuvent accéder aux données dans Redis)
Stockage en mémoire (Redis est stocké dans la mémoire) | Structure de clé et de valeur (Redis est une structure clé-valeur) |
3. Implémenter la connexion à une session partagée basée sur Redis
3.1 Redis implémente un organigramme de connexion à une session partagée
3.2 Implémenter l'envoi du code de vérification par SMSdemande frontale Description :
Description
Méthode de demande
POST
Chemin de la demande
/utilisateur/code
Paramètres de la demandetéléphone (numéro de téléphone) | | Valeur de retour
Aucun | |
Mise en œuvre de l'interface back-end : @Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Override
public Result me() {
UserDTO user = UserHolder.getUser();
return Result.ok(user);
}
}
Copier après la connexion
3.3 Implémenter la connexion et l'enregistrement du code de vérification par SMS | Instructions de demande frontale : |
| | Instructions
| Méthode de demande | P OST
Chemin de la demande
/user/login
Paramètres de la demandetéléphone (numéro de téléphone) ; code (code de vérification) | | Valeur de retour
Aucun | |
Implémentation de l'interface backend :@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public Result sendCode(String phone, HttpSession session) {
// 1. 校验手机号
if (RegexUtils.isPhoneInvalid(phone)) {
// 2. 如果不符合,返回错误信息
return Result.fail("手机号格式错误!");
}
// 3. 符合,生成验证码(设置生成6位)
String code = RandomUtil.randomNumbers(6);
// 4. 保存验证码到 Redis(以手机号为 key,设置有效期为 2min)
stringRedisTemplate.opsForValue().set("login:code:" + phone, code, 2, TimeUnit.MINUTES);
// 5. 发送验证码(这里并未实现,通过日志记录)
log.debug("发送短信验证码成功,验证码:{}", code);
// 返回 ok
return Result.ok();
}
}
Copier après la connexion
3.4 Mettre en œuvre Intercepteur de vérification de l'école de connexion | L'intercepteur d'origine est divisé en deux intercepteurs.Le premier intercepteur intercepte toutes les requêtes.Chaque interception actualise la période de validité du jeton et enregistre les informations utilisateur qui peuvent être interrogées au milieu ThreadLocal. Le deuxième intercepteur exécute la fonction d'interception et intercepte le chemin qui nécessite une connexion. |
Implémentation de l'intercepteur de jeton d'actualisation : @Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
// 1. 校验手机号
String phone = loginForm.getPhone();
if(RegexUtils.isPhoneInvalid(phone)){
// 不一致,返回错误信息
return Result.fail("手机号格式错误!");
}
// 2. 校验验证码
String cacheCode = (String) session.getAttribute("code");
String code = loginForm.getCode();
if(cacheCode == null || !cacheCode.equals(cacheCode)){
// 不一致,返回错误信息
return Result.fail("验证码错误!");
}
// 4. 一致,根据手机号查询用户(这里使用的 mybatis-plus)
User user = query().eq("phone", phone).one();
// 5. 判断用户是否存在
if(user == null){
// 6. 不存在,创建新用户并保存
user = createUserWithPhone(phone);
}
// 7. 保存用户信息到 session 中(通过 BeanUtil.copyProperties 方法将 user 中的信息过滤到 UserDTO 上,即用来隐藏部分信息)
session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));
return Result.ok();
}
private User createUserWithPhone(String phone) {
// 1. 创建用户
User user = new User();
user.setPhone(phone);
user.setNickName("user_" + RandomUtil.randomString(10));
// 2. 保存用户(这里使用 mybatis-plus)
save(user);
return user;
}
}
Copier après la connexion
Copier après la connexion
Implémentation de l'intercepteur de vérification de connexion : | public class RefreshTokenInterceptor implements HandlerInterceptor {
private StringRedisTemplate stringRedisTemplate;
public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate){
this.stringRedisTemplate = stringRedisTemplate;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1. 获取请求头中的 token
String token = request.getHeader("authorization");
if (StrUtil.isBlank(token)) {
return true;
}
// 2. 基于 token 获取 redis 中的用户
String tokenKey = "login:token:" + token;
Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(tokenKey);
// 3. 判断用户是否存在
if (userMap.isEmpty()) {
return true;
}
// 5. 将查询到的 Hash 数据转为 UserDTO 对象
UserDTO user = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
// 6. 存在,保存用户信息到 ThreadLocal
UserHolder.saveUser(user);
// 7. 刷新 token 有效期 30 min
stringRedisTemplate.expire(tokenKey, 30, TimeUnit.MINUTES);
// 8. 放行
return true;
}
}
Copier après la connexion
Implémentation de la classe UserHolder : Cette classe définit un ThreadLocal statique | public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1. 获取 session
HttpSession session = request.getSession();
// 2. 获取 session 中的用户
UserDTO user = (UserDTO) session.getAttribute("user");
// 3. 判断用户是否存在
if(user == null){
// 4. 不存在,拦截,返回 401 未授权
response.setStatus(401);
return false;
}
// 5. 存在,保存用户信息到 ThreadLocal
UserHolder.saveUser(user);
// 6. 放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 移除用户,避免内存泄露
UserHolder.removeUser();
}
}
Copier après la connexion
Copier après la connexion
Intercepteur de configuration : public class UserHolder {
private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();
public static void saveUser(UserDTO user){
tl.set(user);
}
public static UserDTO getUser(){
return tl.get();
}
public static void removeUser(){
tl.remove();
}
}
Copier après la connexion
Description de la demande frontale : | |
Description
Méthode de requête
POST
Chemin de la requête
/user/me
Paramètres de la requêteAucun | | Valeur de retour
Aucun | |
Implémentation de l'interface backend : @Configuration
public class MvcConfig implements WebMvcConfigurer {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate))
.addPathPatterns("/**").order(0);
registry.addInterceptor(new LoginInterceptor())
.excludePathPatterns(
"/user/login",
"/user/code"
).order(1);
}
}
Copier après la connexion
Apprentissage recommandé : | Tutoriel vidéo Redis |
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!