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<String, String> 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<String, String> user = JSON.parseObject(userString, Map.class);

        if (CollectionUtils.isEmpty(user)) {
            log.warn("用户数据为空");
            return;
        }

        userMap = Collections.unmodifiableMap(user);

    }

    /**
     * 获取配置
     *
     * @return
     */
    public static Map<String, String> 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<String, String> 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如下

<body>
<div class="center-login-wrap">
    <div class="center-login">
        <p class="center-login-title">sentinel系统</p>
        <div class="login-form">
            <h3 class="center-login-form-title">用户登录</h3>
            <div class="form-group">
                <input type="text" class="form-control" id="userName" placeholder="用户名">
            </div>
            <div class="form-group">
                <input type="password" class="form-control" id="password" placeholder="密码">
            </div>
            <button type="submit" class="submitBtn" onclick="login();">提交</button>
        </div>
    </div>
</div>
<script>
    function login() {
        const name = document.getElementById('userName').value;
        const password = document.getElementById('password').value;
        if (!name || !password) {
            alert('请输入用户名密码!');
            return;
        }

        fetch('/account/login', {
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                name,
                password
            }),
            method: 'post',
        }).then(res => {
            res.json().then(r => {

                if(r === 0){
                   location.href = '/index.htm';
                }else{
                  alert('用户名密码错误');
                }
           })
       })
     }
</script>
</body>

三、系统权限设置功能

分析

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<String> systems = UserConfig.getUserSystemMap().get(name);      
        //从内存中获取所有系统
        List<AppInfo> list = new ArrayList<>(appManagement.getBriefApps());
        //如果该用户是管理员,则不过滤
        if ("admin".equals(name)) {
            Collections.sort(list, Comparator.comparing(AppInfo::getApp));
            return Result.ofSuccess(list);
        }
        //根据权限过滤
        List<AppInfo> appInfos = new ArrayList<>();
        Map<String, AppInfo> 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能控制通一应用部署的不同机器的限流规则吗

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容

  • 关于Mongodb的全面总结 MongoDB的内部构造《MongoDB The Definitive Guide》...
    中v中阅读 31,931评论 2 89
  • ORA-00001: 违反唯一约束条件 (.) 错误说明:当在唯一索引所对应的列上键入重复值时,会触发此异常。 O...
    我想起个好名字阅读 5,317评论 0 9
  • 以前,我总是不肯动脑筋,每次只要遇到不会做的题,就会去问爸爸妈妈。有一次,爸爸妈妈不在家,我尝到了自己开动脑筋解...
    希希有话说阅读 212评论 0 0
  • 花了2天的时间看了一下《看不见的大猩猩》,总的来说这本书对于人们心理和生理现象的描述还是挺吸引人的,特别是视觉错觉...
    同领地阅读 401评论 0 0
  • #014号梦想家康凯#2018梦想之旅打卡D9/365 我最想实现的梦想:一周三篇文章 我为梦想做了什么:构思文章...
    b2ff155dee25阅读 172评论 0 0