Sentinel之控制台dashboard生产环境改造

一、扩展功能

上一节限流降级框架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如下





三、系统权限设置功能

分析

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能控制通一应用部署的不同机器的限流规则吗

你可能感兴趣的:(Sentinel之控制台dashboard生产环境改造)