springboot的一些项目结构模式,代码写法梳理
1 auth 登录有关服务
该示例演示了对于登录功能的处理。
目录结构:
- controller
- LoginController
- domain
- LoginRequest
- service
- impl
- DefaultLoginService 登录方法
- LoginService
- PasswordService 密码校验
- TokenService 身份验证
- impl
各类主要代码如下:
1.1 LoginController
@Slf4j
@RestController
@ResponseAdvice
@RequestMapping("/login")
public class LoginController {
@Autowired
private LoginService loginService;
@PostMapping("/")
public String login(@RequestBody LoginRequest request) {
return loginService.login(request);
}
}
1.2 LoginRequest
LoginRequest的实体类,表示登录请求的数据。实现了Serializable接口,意味着它可以被序列化。类中有三个私有字段:username表示用户名,password表示密码,vcode表示验证码。
@Data
public class LoginRequest implements Serializable {
private String username;
private String password;
private String vcode;
}
使用@Data注解,Lombok会在编译时为该类的所有非静态字段生成以下方法:
getter方法(如public String getUsername())
setter方法(如public void setUsername(String username))
toString() 方法 equals() 和 hashCode() 方法
默认构造函数(如果没有其他构造函数)
1.3 LoginService
接口,声明一个login方法处理请求
public interface LoginService {
String login(LoginRequest request);
}
1.4 DefaultLoginService 登录校验
LoginService接口的实现,注入1.7 UserServiceClient用于获取用户信息,1.5 PasswordService用于校验密码,1.6 TokenService用于生成和校验token
@Slf4j
@Service("loginService")
public class DefaultLoginService implements LoginService {
@Resource
private UserServiceClient userServiceClient; // 用户服务客户端
@Resource
private PasswordService passwordService; // 密码服务
@Resource
private TokenService tokenService; // Token服务
/**
* 处理用户登录请求。
*
* @param request 包含登录所需信息的请求对象,如用户名和密码。
* @return 经过验证的用户token。
*/
public String login(LoginRequest request) {
// 通过用户名获取用户信息
ResponseWrapper<SysUser> userWrapper = userServiceClient.getUser(request.getUsername());
SysUser user = userWrapper.getData();
// 断言判断用户存在且未被禁用或删除,如果false会抛出异常消息
Assert.nonNull(user, USER_NOT_EXIST);
Assert.isFalse(user.getBlocked(), USER_BLOCKED);
Assert.isFalse(user.getDeleted(), USER_DELETED);
// 验证密码
passwordService.validate(user, request.getPassword());
// 生成并返回token
return tokenService.validate(user);
}
}
1.5 PasswordService 密码验证&用户锁定
@Service //该类为服务组件
public class PasswordService {
@Autowired //注入Redis服务
private RedisService redisService;
/**
* 验证用户密码是否正确,并管理密码验证失败的重试次数,以防止暴力破解。
*
* @param user 表示需要验证的用户对象。
* @param password 用户输入的密码。
* @throws ApiException 如果用户被锁定或者密码不正确时抛出。
*/
public void validate(SysUser user, String password) {
String username = user.getUsername();
// 构造重试次数的Redis键名
String retryKey = retryCountKeyPrefix + username;
// 从Redis获取或默认设置重试次数
Integer retryCount = redisService.getOrDefault(retryKey, 0, Integer.class);
// 确保重试次数未超过最大值,否则抛出用户锁定异常
Assert.isTrue(retryCount < maxRetryCount, USER_LOCKED);
// 当重试次数达到最大值时,锁定用户
if (retryCount >= maxRetryCount) {
// 在Redis中设置用户锁定时间
redisService.set(lockTimeKeyPrefix + username, maxLockTime);
throw new ApiException(ResponseCode.USER_LOCKED); // 抛出用户锁定异常
}
if (!verify(password, user.getPassword())) { // 验证密码失败
redisService.set(retryKey, retryCount + 1); // 更新重试次数
throw new ApiException(ResponseCode.PASSWORD_INCORRECT); // 抛出密码不正确异常
} else { // 密码验证成功
redisService.delete(retryKey); // 清除重试次数
}
}
/**
* 验证输入的密码是否与存储的密码相等。
*
* @param input 用户输入的密码。
* @param saved 存储的密码。
* @return 返回一个布尔值,表示密码是否匹配。
*/
private boolean verify(String input, String saved) {
// 使用Objects静态工具类的equals方法比较两个字符串是否相等
return Objects.equals(input, saved);
}
}
1.6 TokenService 身份验证&token管理
@Service
public class TokenService {
@Resource
private RedisService redisService; // 注入Redis服务,用于token的存储和查询
/**
* 验证用户登录状态,如果不存在有效token,则创建新的token并返回。
*
* @param user 用户信息
* @return 返回有效的token字符串
*/
public String validate(SysUser user) {
String key = loginKeyPrefix + user.getId(); // 生成用户登录key
// 查询缓存中是否存在有效的token
String token = redisService.get(key);
// 如果token不存在或已失效,则重新生成并保存token
if (Objects.isNull(token) || !JwtTokenUtils.isValid(token)) {
redisService.delete(key); // 删除失效的token
token = JwtTokenUtils.generateToken(user.getUsername(), user); // 生成新的token
redisService.set(key, token, expiration); // 新token保存至缓存
return token;
}
// 存在有效token时,检查是否需要刷新
refresh(key);
return token;
}
/**
* 刷新token,当token剩余有效期超过设定时间的1/5时,更新token有效期。
*
* @param key 用户的token存储key
*/
private void refresh(String key) {
// 获取当前token的剩余有效期
long toExpired = redisService.getTime(key);
// 判断是否需要刷新token的有效期
if ((expiration * ratio) > toExpired) {
redisService.expire(key, expiration); // 更新token有效期
}
}
}
1.7 UserServiceClient 远程调用
@FeignClient(name = "system")
//@FeignClient(name = "system", url = "http://localhost:11001")
// 用户服务客户端接口。用于通过Feign进行远程调用用户服务。
public interface UserServiceClient {
/**根据用户名获取用户信息。
*
* @param username 用户名,用于查询特定用户的信息。
* @return 返回用户信息的响应包装器。响应包装器中包含具体的用户信息对象SysUser。
*/
@GetMapping("/user/{username}")
ResponseWrapper<SysUser> getUser(@PathVariable("username") String username);
}