一、扩展功能
上一节限流降级框架Sentinel之dashboard最后说了官方提供的dashboard不足以在生产环境使用。
要扩展的功能
1、登录功能、设置统一拦截器
2、系统权限功能,就是用户只能看到用户权限的系统
3、核心数据持久化
扩展原则
1、不影响原有功能
2、快速简单
二、登录功能
分析
1、登录就要有用户名和密码,这些数据保存在哪?
为了快速开发,用户名和密码保存到son文件中,密码使用MD5加密,在项目启动时候加载进来。后续可以接入到分布式配置中心或数据库。
2、登录状态怎么保存?
最快的方式,就是保存到session中,session设置1个小时过期。
3、首页面怎么设置为登录页面?
重新设置重定向链接,默认地址执行登录页面
4、未登录的用户,请求其他资源怎样设置拦截?
增加一个登录拦截过滤器
5、angularjs我不会,登录页面写在哪?
不要用nagularjs于后端交互,重新写页面,通过ajax请求
实现
1、增加用户信息在文件中,并启动时解析文件
- 在resources中增加use.json文件,文件内容如下:
{
"admin":"21232F297A57A5A743894A0E4A801FC3"
}
用户名和密码都是admin,密码经过MD5加密
- 在config包增加UserConfig类,用来启动时加载用户文件
@Configuration
public class UserConfig {
private static final Logger log = LoggerFactory.getLogger(UserConfig.class);
private static Map userMap;
@PostConstruct
public void initUserConfig() {
String userString = null;
InputStream inStream = null;
try {
inStream = this.getClass().getClassLoader().getResourceAsStream("user.json");
//文件转换为字符串
userString = IOUtils.toString(inStream, "UTF-8");
} catch (IOException e) {
log.error("加载用户数据出现异常,异常是{}", e.getMessage());
throw new RuntimeException("加载用户数据出现异常,请检查");
} finally {
if (inStream != null) {
try {
inStream.close();
} catch (IOException e) {
log.error("close file error {}", e);
}
}
}
Map user = JSON.parseObject(userString, Map.class);
if (CollectionUtils.isEmpty(user)) {
log.warn("用户数据为空");
return;
}
userMap = Collections.unmodifiableMap(user);
}
/**
* 获取配置
*
* @return
*/
public static Map getUserMap() {
return userMap;
}
2、增加用户登录控制器
核心代码如下
@ResponseBody
@RequestMapping(value = "/login", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public Integer login(@RequestBody UserLoginDTO userLoginDTO) {
if (userLoginDTO == null) {
log.debug("user is null ,can't login");
return -1;
}
String name = userLoginDTO.getName();
String password = userLoginDTO.getPassword();
if (StringUtils.isBlank(name) || StringUtils.isBlank(password)) {
log.debug("userName or password can't null");
return -1;
}
//md5加密密码
password = MD5Utils.MD5Encrpytion(password);
//登录
int result = 0;
Map map = UserConfig.getUserMap();
if (!password.equals(map.get(name))) {
return -1;
}
//登录成功,保存用户信息到session中
if (result == 0) {
session.setAttribute(Constants.USER_SESSION_ATTR, name);
//1小时过期
session.setMaxInactiveInterval(60 * 60);
}
return result;
}
3、增加登录拦截器
public class LoginInterceptor extends HandlerInterceptorAdapter {
public static final String USER_SESSION_ATTR = "loginUserName";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//获取请求的用户的session,拿到
if (request.getSession().getAttribute(USER_SESSION_ATTR) != null) {
return true;
}
//未登录跳转到登录页面
response.sendRedirect("/");
return false;
}
}
4、资源拦截配置
改造WebConfig文件
//这里增加static目录,用来保存新增页面的,避免与angularjs页面干扰
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/static/", "classpath:/resources/");
}
//这里跳转重定向路径,默认重定向到登录页面
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("forward:/login.htm");
super.addViewControllers(registry);
}
//这里调价的Login拦截器,并设置不拦截的路径
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/account/*") //登录的请求不拦截
.excludePathPatterns("/registry/*") //这里主要是MachineRegistryController下不拦截,因为应用启动时候要注册服务到dashboard中
.excludePathPatterns("/*");
super.addInterceptors(registry);
}
5、增加登录页面
在resource中增加static包,并增加login.htm登录页面
既可以实现一个跳转页面。
页面及js如下
sentinel系统
用户登录
三、系统权限设置功能
分析
1、多个系统接入了dashboard,系统的名称怎么获取?
自带的AppController中的queryAppInfos方法可以获取到所有注册的客户端
2、每个用户拥有权限的系统怎么定义?
在第一项,用户登录后从session中获取用户登录的用户名,查询该用户拥有的系统权限,在返回层过滤后返回前端
实现
1、用户拥有权限系统定义
如:
{
"user1": [
"app1","app2"
],
"user2": [
"app3"
]
}
这是一个文件,可以命名为userSystem.json,把文件放到应用中,启动时加载到内存中;
user1,user2是两个用户;user1拥有app1,app2两个应用权限,user2拥有app3用户权限。
加载内存通用户加载一样,这里代码就写了,可以看上面用户加载。
2、用户系统权限过滤
改造AppController类,重写queryAppInfos方法,代码如下
//获取用户名,从session中获取
String name = (String) session.getAttribute(Constants.USER_SESSION_ATTR);
//获取用户拥有权限系统
List systems = UserConfig.getUserSystemMap().get(name);
//从内存中获取所有系统
List list = new ArrayList<>(appManagement.getBriefApps());
//如果该用户是管理员,则不过滤
if ("admin".equals(name)) {
Collections.sort(list, Comparator.comparing(AppInfo::getApp));
return Result.ofSuccess(list);
}
//根据权限过滤
List appInfos = new ArrayList<>();
Map map = new HashMap<>();
list.stream().forEach(e -> map.put(e.getApp(), e));
systems.forEach(e -> {
AppInfo appInfo = map.get(e);
if (null != appInfo) {
appInfos.add(appInfo);
}
});
Collections.sort(appInfos, Comparator.comparing(AppInfo::getApp));
return Result.ofSuccess(appInfos);
四、数据持久化
1、哪些数据需要持久化
如:用户登录信息、规则变更信息、异常信息等
2、如何实现数据持久化
可以通过写本地文件方式,sentine限流降级本身l就是通过写本地文件的方式。
还可以写入数据库中,这里就要引入一个新的数据源
五、我的小结
1、这里列举的用户登录权限和系统权限控制的方法
2、用户数据和系统数据通过启动时加载到内存中,如果用户和系统有变更,需要重新修改文件并启动应用,导致不是很灵活;后续可以改造到将这些数据放到数据库中或者分布式配置中心中。
3、数据持久化,没有具体案列,因为这里主要是觉得相对前两个而言,优先级较低。
六、思考
1、dashboard新增一条限流降级规则后,规则如何发送对具体应用中的;应用重启后,这些规则还存在吗?
2、如果一个应用有多台机器部署,且不同机器的配置不一样,dashboard能控制通一应用部署的不同机器的限流规则吗