Chat2Excel 项目用户服务开发

一、在数据库中建库建表

以下为 sql 语句

use bite_excel;

drop table if exists `users`;

CREATE TABLE users

(

id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '用户ID, 主键, 自增长, 从10000001开始',

username VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名, 唯一标识, 用于登录和显示',

email VARCHAR(100) UNIQUE COMMENT '邮箱地址, 唯一, 用于登录和通知',

password_hash VARCHAR(255) COMMENT '密码哈希值, 使用BCrypt加密存储',

INDEX idx_email (email),

INDEX idx_username (username)

) COMMENT '用户表' CHARSET = utf8mb4 AUTO_INCREMENT = 10000001;

二、发送邮箱验证码功能开发

需要完成的功能:1、生成6位数字的字符串的验证码 2、封装发送邮箱服务 3、服务端存储验证码结构

1、接口定义

2、定义请求类对象和响应类对象

java 复制代码
package com.linzhixin.user.dto.request;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;

/**
 * 发送验证码的请求参数
 */
@Data
public class SendCodeRequest {

    @NotBlank(message = "邮箱不能为空")
    @Email(message = "邮箱格式错误") //用于验证字段是否符合邮箱格式。
    private String email;
}
java 复制代码
package com.linzhixin.user.dto.response;

import lombok.Builder;
import lombok.Data;

/**
 * 查询用户信息
 */
@Data
@Builder
public class UserInfoResponse {
    /**
     * 用户 ID
     */
    private Long userId;

    /**
     * 用户名
     */
    private String username;

    /**
     * 邮箱
     */
    private String email;
}

3、创建 UserController

java 复制代码
/**
 * 用户服务的控制器
 */
@RestController
@RequestMapping("/users")
public class UserController {
    /**
     * 发送邮箱验证码
     * @param request
     * @return
     */
    public Result<SendCodeResponse> sendVerificationCode(@RequestBody @Valid SendCodeRequest request) {
    }

4、定义验证码服务相关接口

java 复制代码
package com.linzhixin.user.service;

/**
 *  验证码相关服务接口
 */
public interface VerificationCodeService  {

    /**
     * 发送验证码
     * @param email 邮箱
     * @return 过期时长(秒)
     */
    int sendCode(String email);

5、定义验证码校验服务实现类

java 复制代码
/**
 * 验证码校验服务实现类
 */
@Service
@Slf4j
public class VerificationCodeServiceImpl implements VerificationCodeService {

  
    @Override
    public int sendCode(String email) {
        // 1、生成验证码
        String code = String.valueOf((int)(Math.random()*900000) + 100000);

        return 0;
    }

6、在 common-service 中封装发送QQ邮箱发送邮件的功能

定义邮件发送的接口

java 复制代码
package com.linzhixin.common.service;

/**
 * 邮件发送服务的接口
 */
public interface EmailService {

    /**
     * 发送邮件验证码
     * @param to 收件人的邮箱
     * @param code 验证码
     * @return 是否发送成功
     */
    boolean sendVerificationCode(String to, String code);
}

定义邮件发送的实现类

java 复制代码
/**
 * 发送邮箱的实现类
 */
@Service
@Slf4j
//只有当配置文件中存在 spring.mail.username 这个属性时,才会加载被注解的类或方法。
@ConditionalOnProperty(name = "spring.mail.username")
public class EmailServiceImpl implements EmailService {
    /**
     * spring 邮件发送器
     */
    @Autowired
    private JavaMailSender mailSender;

    /**
     * 发送方邮箱的地址
     */
    @Value("${spring.mail.username}")
    private String fromEmail;


    /**
     * 构建发送验证码的正文
     * @param code 验证码
     * @return 正文
     */
    private String getContent(String code) {
        return String.format("您好!" +
                "您的验证码是: %s\n\n" +
                "验证码的有效期是5分钟,请及时使用",
                code
        );
    }

    @Override
    public boolean sendVerificationCode(String to, String code) {
        // 发送邮箱的逻辑

        SimpleMailMessage message = new SimpleMailMessage();
        message.setFrom(fromEmail);
        message.setTo(to);

        String subject = "系统验证码";
        String content = getContent(code);

        message.setSubject(subject);
        message.setText(content);
        mailSender.send(message);
        log.info("[发送邮箱] 验证码 {} 已发送至 []", code, to);
        return true;
    }




}

7、完善 验证码校验服务实现类 sendCode 方法

java 复制代码
/**
 * 验证码校验服务实现类
 */
@Service
@Slf4j
public class VerificationCodeServiceImpl implements VerificationCodeService {

    @Autowired
    private EmailService emailService;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private RedisService redisService;

    @Override
    public int sendCode(String email) {
        // 1、生成验证码
        String code = String.valueOf((int)(Math.random()*900000) + 100000);

        // 2、发送验证码
        boolean ifSendSuccess = emailService.sendVerificationCode(email, code);

        // 3、查询数据库的判断逻辑 (根据邮箱去查询users表,存在即为登录,不存在即为注册)
        UserEntity user = userMapper.findByLoginKey(email);

        // 新注册
        if(user == null) {
            user = new UserEntity();
            user.setId(0L);
            user.setUserName(email);
        }

        // 验证码信息写到 redis 缓存
        redisService.storeVerificationCode(code, user.getId(), user.getUserName());

        if (ifSendSuccess) {
            log.info("[验证码发送成功], 验证码{} 已经发送至{}", code, email);
            return 300;
        } else {
            log.error("[验证码发送失败], 验证码{} 没有发送至{}", code, email);
            return -1;
        }

    }
}

8、实现 userMapper.findByLoginKey,实现查询数据库的判断逻辑 (根据邮箱去查询users表,存在即为登录,不存在即为注册)

java 复制代码
/**
 * 用户表
 */
@Mapper
public interface UserMapper extends BaseMapper<UserEntity> {

    /**
     * 根据邮箱或者用户名二选一去查询用户
     * @param key 邮箱或者用户名
     * @return 用户
     */
    @Select("select id, username, email, password_hash as passwordHash from users where username=#{key} or email=#{key} limit 1")
    UserEntity findByLoginKey(@Param("key") String key);
}

9、在 common-service 中创建 redisService接口 与 redisServiceImpl 实现类,实现 storeVerificationCode 方法,把 验证码信息写到 redis 缓存

java 复制代码
/**
 * 缓存服务的实现类
 */
@Service
@Slf4j
public class RedisServiceImpl implements RedisService {

    // StringRedisTemplate: Spring 提供的、专门操作 Redis 字符串数据的工具类
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    private final ObjectMapper objectMapper = new ObjectMapper();

    /**
     * 验证码令牌前缀
     */
    private static final String CODE_PREFIX = "code";


    @Override
    public void storeVerificationCode(String code, Long userId, String userName) {
        // 1、先生成 key
        String key = CODE_PREFIX + code;
        CodeInfo codeInfo = new CodeInfo(userId, userName);

        // 2、对象序列化之后,写入redis
        try {
            String value = objectMapper.writeValueAsString(codeInfo);
            stringRedisTemplate.opsForValue().set(key, value, 300, TimeUnit.SECONDS);
            log.info("[Redis存储成功] 验证码 {} 用户名 {}", code, userName);
        } catch (JsonProcessingException e) {
            log.error("[Redis存储失败] 验证码 {}  用户名 {}", code, userName);
        }
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    private static class CodeInfo {
        /**
         * 用户ID
         */
        private Long userId;

        /**
         * 用户名:邮箱或者用户名
         */
        private String userName;
    }
}

10、完善 UserController 发送验证码方法 + 编写启动类

java 复制代码
**
 * 用户服务的控制器
 */
@RestController
@RequestMapping("/users")
public class UserController {
    @Autowired
    private VerificationCodeService verificationCodeService;

    @Autowired
    private UserService userService;


    /**
     * 发送邮箱验证码
     * @param request
     * @return
     */
    @PostMapping("/verification-code")
    @LogOperation("发送验证码")
    public Result<SendCodeResponse> sendVerificationCode(@RequestBody @Valid SendCodeRequest request) {
        int expireSeconds = verificationCodeService.sendCode(request.getEmail());
        String sendTo = maskEmail(request.getEmail());
        SendCodeResponse response = SendCodeResponse.builder()
                .expireTime(expireSeconds)
                .sendTo(sendTo)
                .build();
        return Result.success("验证码发送成功", response);
    }

    /**
     * 邮箱脱敏
     * @param email 邮箱
     * @return 脱敏后的邮箱
     */
    private String maskEmail(@NotBlank(message = "邮箱不能为空") @Email(message = "邮箱格式错误") String email) {
        String[] parts = email.split("@", 2);
        String name = parts[0];
        String domain = parts[1];
        if(name.length() <= 2) {
            return name.charAt(0) + "***@" + domain;
        }
        return name.substring(0, 2) + "***@" + domain;
    }

}
java 复制代码
@SpringBootApplication
@MapperScan("com.linzhixin.user.mapper")
@ComponentScan(basePackages = {
        "com.linzhixin.user",
        "com.linzhixin.common"
})
@Slf4j
public class UserApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserApplication.class, args);
        log.info("Port: 9001");
    

11、调用接口测试发送验证码功能

http://localhost:8080/api/v1/users/verification-code

三、注册+登录功能实现

1、接口定义

2、实现校验验证码功能

定义DTO(AuthRequest+AuthResponse)

java 复制代码
package com.linzhixin.user.dto.request;

import lombok.Data;

/**
 * 统一认证请求参数(注册+登录)
 */
@Data
public class AuthRequest {
    /**
     * 用户名
     */
    private String username;

    /**
     * 密码
     */
    private String password;

    /**
     * 邮箱
     */
    private String email;

    /**
     * 验证码
     */
    private String verificationCode;
}
java 复制代码
package com.linzhixin.user.dto.response;

import lombok.Builder;
import lombok.Data;

/**
 * 统一认证响应参数(注册+登录)
 */
@Data
@Builder
public class AuthResponse {
    /**
     * 用户ID
     */
    private Long userId;

    /**
     * 用户名
     */
    private String userName;

    /**
     * 邮箱
     */
    private String email;

    /**
     * 用户访问令牌
     */
    private String token;

    /**
     * 令牌的过期时间
     */
    private String tokenExpireTime;

    /**
     * 是否为新用户
     */
    private Boolean isNewUser;
}

服务层代码编写

实现逻辑

完善 VerificationCodeService 与 VerificationCodeServiceImpl

VerificationCodeServiceImpl类

java 复制代码
@Override
    public boolean verifyCode(String email, String code) {
        return redisService.isCodeValid(code);
    }

RedisServiceImpl 类

java 复制代码
@Override
    public boolean isCodeValid(String code) {
        // 1、先生成 key
        String key = CODE_PREFIX + code;
        return stringRedisTemplate.hasKey(key);
    }

3、实现验证码注册或登录功能

定义接口与实现类

java 复制代码
/**
 * 用户服务相关接口
 */
public interface UserService {
    AuthResponse auth(AuthRequest authRequest);
}
java 复制代码
/**
 * 用户服务实现类
 */
@Service
@Slf4j
public class UserServiceImpl implements UserService {

    @Autowired
    private VerificationCodeService verificationCodeService;

    @Autowired
    private UserMapper userMapper;


    @Override
    public AuthResponse auth(AuthRequest authRequest) {
        // 1、先验证当前是哪种方式(邮箱+验证码/账号+密码)
        boolean isEmailCodeMode = StringUtils.isNotBlank(authRequest.getEmail()) && StringUtils.isNotBlank(authRequest.getVerificationCode());
        boolean isPasswordMode = StringUtils.isNotBlank(authRequest.getUsername()) && StringUtils.isNotBlank(authRequest.getPassword());

        if(!isEmailCodeMode && !isPasswordMode) {
            throw new IllegalArgumentException("请提供有效的验证方式:邮箱+验证码/账号+密码");
        }

        UserEntity user = null;
        Boolean isNewUser = false;
        // 2、先处理邮箱+验证码的方式
        if(isEmailCodeMode) {
            if(!verificationCodeService.verifyCode(authRequest.getEmail(), authRequest.getVerificationCode())) {
                throw new IllegalArgumentException("验证码无效或者过期");
            }

            // 3、注册还是登录
            Long userId = verificationCodeService.getUserId(authRequest.getVerificationCode());

            // 4、新用户来注册
            if(userId == 0L) {
                user = new UserEntity();
                user.setEmail(authRequest.getEmail());
                user.setUserName(createRandomName());
                userMapper.insert(user);
                isNewUser = true;
            }

            // 老用户登录,直接查询数据库完善user信息
            user = userMapper.selectById(userId);

            // 5、删除验证码,避免重复使用
            verificationCodeService.remove(authRequest.getVerificationCode());
        }

       
    }

        
    /**
     * 用来生成随机用户名
     * @return
     */
    private String createRandomName() {
        String name = "hello_" + String.valueOf((int)(Math.random() * 900000000) + 100000000);
        // 1、用户名需要全局唯一
        if(userMapper.existByUserName(name) == 0) {
            return name;
        }

        return  "hello_" + System.currentTimeMillis();
    }
}

VerificationCodeServiceImpl 类

java 复制代码
    @Override
    public Long getUserId(String code) {
        return redisService.getUserIdByCode(code);
    }

    @Override
    public void remove(String code) {
        redisService.remove(code);
    }

RedisServiceImpl 类

java 复制代码
    @Override
    public Long getUserIdByCode(String code) {
        // 1、先生成key
        String key = CODE_PREFIX + code;
        String value = stringRedisTemplate.opsForValue().get(key);

        if(value != null) {
            try {
                CodeInfo codeInfo = objectMapper.readValue(value, CodeInfo.class);
                return codeInfo.userId;
            } catch (JsonProcessingException e) {
                log.error("[Redis读取失败] 验证码 {}  报错 {}", code, e.getMessage());
            }
        }
        return null;
    }

    @Override
    public void remove(String code) {
        String key = CODE_PREFIX + code;
        stringRedisTemplate.delete(key);
        log.info("验证码已经删除{}", code);
    }

后续 处理 token 方式等 账号密码注册或登录功能 实现后一起处理

4、实现账号密码注册或登录功能

完善 UserServiceImpl 类 auth 方法

java 复制代码
if(isPasswordMode) {
	user = userMapper.findByLoginKey(authRequest.getUsername());

	if(user == null) {
		// 新用户,需要先注册
		user = new UserEntity();
		user.setUserName(authRequest.getUsername());
		user.setPasswordHash(passwordEncoder.encode(authRequest.getPassword()));
		userMapper.insert(user);
		isNewUser = true;
	else {
		// 老用户,校验密码
		if(!passwordEncoder.matches(authRequest.getPassword(), user.getPasswordHash())) {
			throw new IllegalArgumentException("用户名与密码不匹配");
		}
	}
}

UserMapper.findByLoginKey()

java 复制代码
    /**
     * 根据邮箱或者用户名二选一去查询用户
     * @param key 邮箱或者用户名
     * @return 用户
     */
    @Select("select id, username, email, password_hash as passwordHash from users where username=#{key} or email=#{key} limit 1")
    UserEntity findByLoginKey(@Param("key") String key);

5、获取 token 作为返回值

完善 UserServiceImpl 类 auth 方法

java 复制代码
        // 处理 token
        String token;

        // 重复登录直接获取
        if(jwtUtil.isUserLogged(user.getId())) {
            token = jwtUtil.getUserActiveToken(user.getId());
        } else {
            token = jwtUtil.createToken(user.getId(), user.getUserName());
        }

        LocalDateTime time = LocalDateTime.now();

        LocalDateTime expireTime = time.plusHours(20);

        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM-dd HH:mm:ss");
        String tokenExpireTime = expireTime.format(formatter);

        return AuthResponse.builder()
                .userId(user.getId())
                .userName(user.getUserName())
                .email(user.getEmail())
                .token(token)
                .tokenExpireTime(tokenExpireTime)
                .isNewUser(isNewUser)
                .build();

在 common-service 中创建 JwtUtil 并完善

java 复制代码
package com.linzhixin.common.util;


import com.linzhixin.common.service.RedisService;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Date;

/**
 * JWT 工具类:复杂生产、校验、管理用户登录令牌
 */
@Component
public class JwtUtil {
    @Autowired
    private RedisService redisService;

    @Value("72000")
    private Long expireTime;

    /**
     * 签名密钥对象
     */
    private SecretKey key;


    /**
     * H256密钥
     */
    @Value("changeit-change-it-change-it-change-it-change-it")
    private String secret;

    /**
     * 初始化密钥对象
     */
    //@PostConstruct:
    //在 Spring 容器完成 Bean 的依赖注入后,自动执行这个方法
    //确保在业务方法使用 key 之前,它已经被初始化好
    @PostConstruct
    private void init() {
        //// 1. 将字符串密钥转为字节数组(UTF-8 编码)
        byte[] keyBytes = secret.getBytes(StandardCharsets.UTF_8);
        // 2. 使用 HMAC-SHA 算法生成 Key 对象
        this.key = Keys.hmacShaKeyFor(keyBytes);
    }

    /**
     * 生成 JWT 令牌并写入缓存
     * @param userId 用户ID
     * @param username 用户名或邮箱
     * @return 令牌
     */
    public String createToken(Long userId, String username) {
        Date now = new Date();

        Date expiry = new Date(now.getTime() + expireTime);

        String token = Jwts.builder()
                .setSubject(String.valueOf(userId))
                .claim("username", username)
                .setIssuedAt(now)
                .setExpiration(expiry)
                .signWith(key, SignatureAlgorithm.HS256)
                .compact();
        // token 存到 redis
        redisService.storeToken(token, userId, username, expireTime);

        // 存储活跃用户(避免重复登陆)
        redisService.storeUserActiveToken(userId, token, expireTime);

        return token;
    }


    /**
     * 获取当前用户的token
     * @param userId 用户ID
     * @return 用户token
     */
    public String getUserActiveToken(Long userId) {
        return redisService.getUserToken(userId);
    }

}

redisServiceImpl.storeToken() 、 redisServiceImpl.storeUserActiveToken、

redisServiceImpl.getUserToken()

java 复制代码
    /**
     * 个人令牌前缀
     */
    private static final String TOKEN_PREFIX = "token:";


    @Override
    public void storeToken(String token, Long userId, String username, Long expireSeconds)             {
        String key = TOKEN_PREFIX + token;
        TokenInfo tokenInfo = new TokenInfo(userId, username);
        try {
            String value = objectMapper.writeValueAsString(tokenInfo);
            stringRedisTemplate.opsForValue().set(key, value, expireSeconds, TimeUnit.SECONDS);
        } catch (JsonProcessingException e) {
            log.error("[Redis令牌存储失败 {} {}]", token, e.getMessage());
        }
    }

    /**
     * 检查用户是否已经登录
     * @param userId 用户ID
     * @return 是否登录
     */
    public boolean isUserLogged(Long userId) {
        return redisService.isUserLogged(userId);
    }


    @Override
    public void storeUserActiveToken(Long userId, String token, Long expireSeconds) {
        String key = USER_SESSION_PREFIX + userId;
        stringRedisTemplate.opsForValue().set(key, token, expireSeconds, TimeUnit.SECONDS);
    }
    
    @Override
    public String getUserToken(Long userId) {
        String key = USER_SESSION_PREFIX + userId;
        return stringRedisTemplate.opsForValue().get(key);
    }

四、获取用户信息功能实现

1、接口定义

2、UserController

java 复制代码
/**
     * 用户信息查询
     */
    @GetMapping("/info")
    @LogOperation("用户信息查询")
    public Result<UserInfoResponse> getUserInfo(@RequestHeader(value = "Authorization", required = true) String authorization) {
        return Result.success("success", userService.getUserInfo(authorization));
    }

3、定义 响应类 UserInfoResponse

java 复制代码
package com.linzhixin.user.dto.response;

import lombok.Builder;
import lombok.Data;

/**
 * 查询用户信息
 */
@Data
@Builder
public class UserInfoResponse {
    /**
     * 用户 ID
     */
    private Long userId;

    /**
     * 用户名
     */
    private String username;

    /**
     * 邮箱
     */
    private String email;
}

4、UserServiceImpl

java 复制代码
    @Override
    public UserInfoResponse getUserInfo(String authorization) {
        // 1、去 JWT 工具类里面查询用户ID
        Long userId = jwtUtil.getUserIdByAuthorization(authorization);
        if(userId == null) {
            throw new IllegalArgumentException("无效的令牌");
        }

        UserEntity user = userMapper.selectById(userId);
        if(user == null) {
            throw new IllegalArgumentException("用户不存在");
        }

        return UserInfoResponse.builder()
                .userId(user.getId())
                .username(user.getUserName())
                .email(user.getEmail())
                .build();
    }

5、JwtUtil

java 复制代码
    /**
     * 获取用户ID
     * @param authorization 前端传递的令牌
     * @return 用户ID
     */
    public Long getUserIdByAuthorization(String authorization) {
        // 1、从 redis 中去获取
        return redisService.getUserIdByAuthorization(authorization);
    }

五、修改密码功能实现

1、接口定义

2、UserController

java 复制代码
    /**
     * 修改密码
     */
    @PostMapping("/change-password")
    @LogOperation("修改密码")
    public Result<ChangePasswordResponse> changePassword(
            @RequestHeader(value = "Authorization", required = true) String authorization,
            @RequestBody @Valid ChangePasswordRequest request) {
        return Result.success("success", userService.changePassword(request, authorization));
    }

3、定义请求类ChangePasswordRequest和响应类ChangePasswordResponse

java 复制代码
/**
 * 修改密码的请求参数
 */
@Data
@Builder
public class ChangePasswordRequest {
    /**
     * 当前的密码
     */
    @NotBlank(message = "当前密码必填")
    private String currentPassword;

    /**
     * 新密码
     */
    @NotBlank(message = "新密码不能为空")
    @Size(min = 6, max = 64, message = "新密码长度介于6到64个字符之间")
    private String newPassword;


    /**
     * 确认密码必填
     */
    @NotBlank(message = "确认密码不能为空")
    private String confirmPassword;
}
java 复制代码
/**
 * 查询用户信息
 */
@Data
@Builder
public class UserInfoResponse {
    /**
     * 用户 ID
     */
    private Long userId;

    /**
     * 用户名
     */
    private String username;

    /**
     * 邮箱
     */
    private String email;
}

4、UserService 类

java 复制代码
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ChangePasswordResponse changePassword(ChangePasswordRequest request, String token) {
        // 1、新密码和确认密码必须一致
        if(!request.getNewPassword().equals(request.getConfirmPassword())) {
            throw new IllegalArgumentException("请保证新旧密码一致");
        }

        // 2、新密码与老密码不能相同
        if(request.getCurrentPassword().equals(request.getNewPassword())) {
            throw new IllegalArgumentException("新旧密码不能相同");
        }

        // 3、根据 token 获取用户信息
        Long userId = jwtUtil.getUserIdByAuthorization(token);
        if(userId == null) {
            throw new IllegalArgumentException("令牌无效");
        }

        // 4、获取到用户信息之后进行修改操作
        UserEntity user = userMapper.selectById(userId);

        user.setPasswordHash(passwordEncoder.encode(request.getNewPassword()));
        userMapper.updateById(user);

        // 5、封装响应
        return ChangePasswordResponse.builder()
                .userId(userId)
                .username(user.getUserName())
                .build();
    }

5、JwtUtil

java 复制代码
     /**
     * 获取用户ID
     * @param authorization 前端传递的令牌
     * @return 用户ID
     */
    public Long getUserIdByAuthorization(String authorization) {
        // 1、从 redis 中去获取
        return redisService.getUserIdByAuthorization(authorization);
    }

6、RedisServiceImpl

java 复制代码
     @Override
    public String getUserToken(Long userId) {
        String key = USER_SESSION_PREFIX + userId;
        return stringRedisTemplate.opsForValue().get(key);
    }

六、用户登出

1、接口定义

2、UserController

java 复制代码
    /**
     * 用户退出系统
     */
    @PostMapping("/logout")
    @LogOperation("用户退出")
    public Result<Void> logout(@RequestHeader(value = "Authorization", required = true) String authorization) {
        userService.logout(authorization);
        return Result.success("登出成功", null);
    }

3、UserServiceImpl

java 复制代码
    @Override
    public void logout(String authorization) {
        jwtUtil.removeAuthorization(authorization);
    }

4、JwtUtil

java 复制代码
     /**
     * 删除令牌
     * @param authorization 用户令牌
     */
    public void removeAuthorization(String authorization) {
        redisService.removeAuthorization(authorization);
    }

5、RedisServiceImpl

java 复制代码
    @Override
    public void removeAuthorization(String authorization) {
        // 1、获取原生的 token
        String token = authorization.replaceFirst("(?i)^Bearer ", "").trim();
        // 2、删除用户令牌缓存
        String tokenKey = TOKEN_PREFIX + token;
        Long userId = getUserIdByAuthorization(token);
        stringRedisTemplate.delete(tokenKey);
        // 3、删除活跃用户缓存令牌
        String userKey = USER_SESSION_PREFIX + userId;
        stringRedisTemplate.delete(userKey);
    }
相关推荐
邪修king1 小时前
MySQL数据库(二):操作详解:从创建、查看到备份恢复
数据库·mysql
CodeStats1 小时前
【Java数据类型】Java底层数据类型原理(下):时间API、JSON与String存储演进
java·开发语言·数据类型·string·byte
Co_Hui1 小时前
Java Synchronized 关键字
java
lhldsg1 小时前
折扣卡CPS小程序定制全流程实战指南:从需求分析到上线部署
java·前端·小程序
她的男孩1 小时前
多租户隔离怎么落地?拆完这1600行Starter源码,我把5个坑全踩明白了
java·后端·架构
kiss strong1 小时前
redis服务器登录(内网环境,无法使用客户端)
数据库·redis·缓存
ly76891 小时前
Spring 事件机制在微服务中实现最终一致性的设计
java·spring·微服务·异步处理·最终一致性·spring事件·事务事件
wuminyu1 小时前
Kafka利用sendfile与Page Cache实现高性能传输剖析
java·linux·c语言·jvm·c++
我命由我123451 小时前
Android 开发问题:android.permission.CAMERA...duplicated with element declared at
android·java·java-ee·android studio·android jetpack·android-studio·android runtime