背景
在我工作的项目中使用了RuoYi-Vue-Plus,是一套分布式的框架,支持多租户。
而这次要讲到的案例是一个关于登录的流程,该流程涉及到鉴权模块(Auth)
和系统模块(System)
,前端在执行登录操作时,先携带登录信息向Auth发起请求。Auth拿到用户名再调用Sytem模块Feign接口,通过userId获取用户信息的,最终比对密码是否正确。
示例代码
Auth模块
实体
@Data
public class LoginBody {
private String username;
private String password;
}
Controller
@RestController
@RequiredArgsConstructor
public class AuthColler {
private final RemoteUserService remoteUserService;
/**
* 登录方法
*/
@PostMapping("/login")
public R<LoginVo> login(@RequestBody LoginBody body) {
// 调用Feign接口获取用户信息
SysUser user = remoteUserService.getUserInfo(username);
// 判断是否有效
if(user == null) {
throw new RuntimeException("用户" + body.getUsername + "不存在");
}
if(user.getTenantId == null) {
throw new RuntimeException("用户" + body.getUsername + "租户id不应该为空");
}
// 封装成vo并返回前端
LoginVo loginVo = BeanUtils.copyProperties(user, LoginVo.class);
return R.ok(loginVo);
}
}
System模块
Feign接口
@FeignClient("system")
public interface RemoteUserService {
@GetMapping({"/user/getUserInfoByUserName"})
LoginUser getUserInfo(String username)
}
Controller
/**
* 用户信息
*/
@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
public class SysUserController {
private final SysUserService userService;
/**
* 通过用户名查询用户信息
*/
@GetMapping("/getUserInfoByUserName")
public SysUser getUserInfo(String username) {
// 假设Service层调用Dao层查找了数据
SysUser sysUser = userService.getUserInfoByUserName(username);
if (ObjectUtil.isNull(sysUser)) {
throw new RuntimeException("用户" + username + "不存在");
}
return sysUser;
}
}
出现的问题
当使用不存在的用户名测试登录时,登录接口报的错不是用户不存在
,而是租户id不应该为空
。
如果查找System模块的日志,却看到System模块正确抛出了用户不存在
的异常,那为什么Auth模块没能正确处理?
问题点
问题出现的原因就在System模块接口的返回值上,该接口并没有使用类似Auth模块代码里的R<?>
作为统一返回。并且Controller使用了@RestController
注解,意味着调用结果会通过Json字符串的格式返回了用户不存在
的异常数据。
那么Feign接口的调用方在收到响应的Json字符串会做什么?如果字符串不为空,会创建对象
并尝试映射值到对象的每一个字段。问题点就在这,既然对象已经创建,判空就没有意义,自然不能正确处理用户不存在的情况。
反思
从这个案例不难看出,远程过程调用的接口选择在Controller层的原因,以及统一返回的重要性。一方面远程过程调用的结果不像是在同个进程内部的代码一样可控,这种调用更接近前端到后端的两端调用关系。另一方面,使用统一返回和自定义的状态码,可以更加灵活地判断调用结果,根据结果不同进行相应的处理。