健康管理项目第八天(后台登录实现认证和授权、图形报表Echarts)

本文介绍如何在后台系统中应用SpringSecurity框架进行权限控制,包括认证和授权的实现过程,以及如何利用ECharts展示会员数量的折线图,实现数据的可视化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前面我们已经学习了Spring Security框架的使用方法,本章节我们就需要将Spring Security框架应用到后台系统中进行权限控制,其本质就是认证和授权。
要进行认证和授权需要前面课程中提到的权限模型涉及的7张表支撑,因为用户信息、权限信息、菜单信息、角色信息、关联信息等都保存在这7张表中,也就是这些表中的数据 是我们进行认证和授权的依据。所以在真正进行认证和授权之前需要对这些数据进行管 理,即我们需要开发如下一些功能:
1、权限数据管理(增删改查)
2、菜单数据管理(增删改查)
3、角色数据管理(增删改查、角色关联权限、角色关联菜单)
4、用户数据管理(增删改查、用户关联角色)
鉴于时间关系,我们不再实现这些数据管理的代码开发。我们可以直接将数据导入到数据库中即可。

一、在项目中应用Spring Security
1.1 导入Spring Security环境

第一步:在health_parent父工程的pom.xml中导入Spring Security的maven坐标(在搭建项目环境的时候,已经导入)

第二步:在health_backend工程的web.xml文件中配置用于整合Spring Security框架的过滤器DelegatingFilterProxy

<!--委派过滤器,用于整合其他框架-->
<filter>
  <!--整合spring security时,此过滤器的名称固定springSecurityFilterChain-->
  <filter-name>springSecurityFilterChain</filter-name>
  <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
  <filter-name>springSecurityFilterChain</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>
1.2 实现认证和授权

第一步:在health_backend工程中按照Spring Security框架要求提供 SpringSecurityUserService,并且实现UserDetailsService接口。

package com.oracle.service;

import com.alibaba.dubbo.config.annotation.Reference;
import com.oracle.pojo.Permission;
import com.oracle.pojo.Role;
import com.oracle.pojo.User;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import java.util.ArrayList;
import java.util.List;

public class SpringSecurityUserService implements UserDetailsService {

    @Reference
    UserService userService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //1.根据用户名查找用户信息
        User user = userService.findUserByUsername(username);
        if(user==null){
            return null;
        }
        //2.将从数据库中查询出来的权限和角色封装进springsecurity框架中返回回去
        List<GrantedAuthority> list=new ArrayList<>();
        for (Role role : user.getRoles()) {
            list.add(new SimpleGrantedAuthority(role.getKeyword()));
            for (Permission permission : role.getPermissions()) {
                list.add(new SimpleGrantedAuthority(permission.getKeyword()));
            }
        }
        return new org.springframework.security.core.userdetails.User(username,user.getPassword(),list);
    }
}

第二步:创建UserService服务接口、服务实现类、Dao接口、Mapper映射文件等

//在health_interface工程下面创建接口
package com.oracle.service;

import com.oracle.pojo.User;

public interface UserService {
    //根据用户名查找用户信息
    User findUserByUsername(String username);
}
//UserServiceIpml
package com.oracle.service;

import com.alibaba.dubbo.config.annotation.Service;
import com.oracle.dao.PermissionDao;
import com.oracle.dao.RoleDao;
import com.oracle.dao.UserDao;
import com.oracle.pojo.Permission;
import com.oracle.pojo.Role;
import com.oracle.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

import java.util.Set;

@Service(interfaceClass = UserService.class)
@Transactional
public class UserServiceImpl implements UserService {

    @Autowired
    UserDao userDao;

    @Autowired
    RoleDao roleDao;

    @Autowired
    PermissionDao permissionDao;

    @Override
    public User findUserByUsername(String username) {
        //根据用户名查找用户基本信息
        User user = userDao.findByUsername(username);
        if(user==null){
            return null;
        }
        //根据用户id得到用户角色信息
        Set<Role> roles=roleDao.findRoleByUserId(user.getId());
        //如果用户角色不为空,遍历角色得到对应角色的权限信息,并封装进role中
        if(roles!=null||roles.size()>0){
            for (Role role : roles) {
                Set<Permission> permissions=permissionDao.findPermissionByRoleId(role.getId());
                if(permissions!=null||permissions.size()>0){
                    role.setPermissions(permissions);
                }
            }
            user.setRoles(roles);
        }
        return user;
    }
}
//UserDao 及对应的Mapper文件
public interface UserDao {
     User findByUsername(String username);
}
//UserDao 及对应的Mapper文件
<mapper namespace="com.bianyi.dao.UserDao">
    <select id="findByUsername" parameterType="String" resultType="com.bianyi.pojo.User">
          select * from t_user where username = #{username}
    </select>
</mapper>
//RoleDao及对应的Mapper文件
public interface RoleDao {
    Set<Role> findByUserId(int id);
}
//RoleDao及对应的Mapper文件
<mapper namespace="com.bianyi.dao.RoleDao">
    <select id="findByUserId" parameterType="int" resultType="com.bianyi.pojo.Role">
        SELECT *  FROM t_role WHERE  id  IN(
              SELECT role_id  FROM t_user_role WHERE user_id=#{userId})
    </select>
</mapper>
//PermissionDao及对应的Mapper文件
public interface PermissionDao {
    Set<Permission> findByRoleId(int roleId);
}
//PermissionDao及对应的Mapper文件
<mapper namespace="com.bianyi.dao.PermissionDao">
    <select id="findByRoleId" parameterType="int" resultType="com.bianyi.pojo.Permission">
        SELECT *  FROM t_permission WHERE id IN(
              SELECT  permission_id FROM t_role_permission WHERE role_id=#{roleId}
    )
    </select>
</mapper>

第三步:修改health_backend工程中的springmvc.xml文件,修改dubbo批量扫描的包路径

<!--批量扫描-->
<dubbo:annotation package="com.oracle" />


注意:此处原来扫描的包为com.oracle.controller,现在改为com. oracle包的目的是 需要将我们上面定义的SpringSecurityUserService也扫描到,因为在 SpringSecurityUserService的loadUserByUsername方法中需要通过dubbo远程调用名称为UserService的服务。

第四步:在health_backend工程中提供spring-security.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:security="http://www.springframework.org/schema/security"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/mvc
       http://www.springframework.org/schema/mvc/spring-mvc.xsd
       http://code.alibabatech.com/schema/dubbo
       http://code.alibabatech.com/schema/dubbo/dubbo.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/security
       http://www.springframework.org/schema/security/spring-security.xsd">

    <!--配置哪些资源匿名可以访问(不登录也可以访问)-->
    <security:http security="none" pattern="/login.html"></security:http>
    <security:http security="none" pattern="/css/**"></security:http>
    <security:http security="none" pattern="/img/**"></security:http>
    <security:http security="none" pattern="/js/**"></security:http>
    <security:http security="none" pattern="/plugins/**"></security:http>
    <!--
        auto-config:自动配置,如果设置为true,表示自动应用一些默认配置,比如框架会提供一个默认的登录页面
        use-expressions:是否使用spring security提供的表达式来描述权限
    -->
    <security:http auto-config="true" use-expressions="true">
        <security:headers>
            <!--设置在页面可以通过iframe访问受保护的页面,默认为不允许访问-->
            <security:frame-options policy="SAMEORIGIN"></security:frame-options>
        </security:headers>
        <!--配置拦截规则,/** 表示拦截所有请求-->
        <!--
            pattern:描述拦截规则
            asscess:指定所需的访问角色或者访问权限
        -->
        <!--只要认证通过就可以访问-->
        <security:intercept-url pattern="/pages/**"  access="isAuthenticated()" />

        <!--如果我们要使用自己指定的页面作为登录页面,必须配置登录表单.页面提交的登录表单请求是由框架负责处理-->
        <!--
            login-page:指定登录页面访问URL
        -->
        <security:form-login
                login-page="/login.html"
                username-parameter="username"
                password-parameter="password"
                login-processing-url="/login.do"
                default-target-url="/pages/main.html"
                authentication-failure-url="/login.html"></security:form-login>

        <!--
          csrf:对应CsrfFilter过滤器
          disabled:是否启用CsrfFilter过滤器,如果使用自定义登录页面需要关闭此项,否则登录操作会被禁用(403)
        -->
        <security:csrf disabled="true"></security:csrf>

        <!--
          logout:退出登录
          logout-url:退出登录操作对应的请求路径
          logout-success-url:退出登录后的跳转页面
        -->
        <security:logout logout-url="/logout.do"
                         logout-success-url="/login.html" invalidate-session="true"/>

    </security:http>

    <!--配置认证管理器-->
    <security:authentication-manager>
        <!--配置认证提供者-->
        <security:authentication-provider user-service-ref="springSecurityUserService">
            <!--指定度密码进行加密的对象-->
            <security:password-encoder ref="passwordEncoder"></security:password-encoder>
        </security:authentication-provider>
    </security:authentication-manager>

    <!--配置密码加密对象-->
    <bean id="passwordEncoder"
          class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />

    <!--开启注解方式权限控制-->
    <security:global-method-security pre-post-annotations="enabled" />

    <bean id="springSecurityUserService" class="com.oracle.service.SpringSecurityUserService"></bean>
</beans>

第五步:在springmvc.xml文件中引入spring-security.xml文件

<import resource="spring-security.xml"></import>

第六步:在Controller的方法上加入权限控制注解,此处以CheckItemController为例

第七步:修改页面,没有权限时提示信息设置,此处以checkitem.html中的handleDelete方法为例

//权限不足的提示
showMessage(r){
    if(r == 'Error: Request failed with status code 403'){
        //权限不足
        this.$message.error('无访问权限');
        return;
    }else{
        this.$message.error('未知错误');
        return;
    }
},

1.3 显示用户名

前面我们已经完成了认证和授权操作,如果用户认证成功后需要在页面展示当前用户的用户名。Spring Security在认证成功后会将用户信息保存到框架提供的上下文对象中, 所以此处我们就可以调用Spring Security框架提供的API获取当前用户的username并展示到页面上。

实现步骤:
第一步:在main.html页面中修改,定义username模型数据基于VUE的数据绑定展示用 户名,发送ajax请求获取username。

第二步:创建UserController并提供getUsername方法

package com.oracle.controller;

import com.oracle.constant.MessageConstant;
import com.oracle.entity.Result;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class UserController {
    //获取用户的登录名
    @RequestMapping("getUserName")
    public Result getUserName(){
        //当Spring security完成认证后,会将当前用户信息保存到框架提供的上下文对象
        User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        if(user!=null){
            String username = user.getUsername();
            return new Result(true, MessageConstant.GET_USERNAME_SUCCESS,username);
        }
        return new Result(false, MessageConstant.GET_USERNAME_FAIL);
    }
}

1.4 用户退出

第一步:在main.html中提供的退出菜单上加入超链接

第二步:在spring-security.xml文件中配置

二、图形报表ECharts
3.1 ECharts简介

ECharts缩写来自Enterprise Charts,商业级数据图表,是百度的一个开源的使用 JavaScript实现的数据可视化工具,可以流畅的运行在 PC 和移动设备上,兼容当前绝大 部分浏览器(IE8/9/10/11,Chrome,Firefox,Safari等),底层依赖轻量级的矢量图 形库 ZRender,提供直观、交互丰富、可高度个性化定制的数据可视化图表。

官网:https://echarts.baidu.com/ 
下载地址:https://echarts.baidu.com/download.html



我们只需要将dist目录下的echarts.js文件引入到页面上就可以使用了

2.2 五分钟上手ECharts

我们可以参考官方提供的5分钟上手ECharts文档感受一下ECharts的使用方式,地址如下: https://www.echartsjs.com/tutorial.html#5%20%E5%88%86%E9%92%9F%E4%B8%8 A%E6%89%8B%20ECharts
第一步:创建html页面并引入echarts.js文件

<!--引入 ECharts 文件-->
<script src="echarts.js"></script>

第二步:在页面中准备一个具备宽高的DOM容器。

<body>
  	<!--为 ECharts 准备一个具备大小(宽高)的 DOM-->
	<div id="main" style="width: 600px;height:400px;"></div>
</body>

第三步:通过echarts.init方法初始化一个echarts实例并通过setOption方法生成一个简单的柱状图

<script>
    // 基于准备好的dom,初始化echarts实例
    var myChart = echarts.init(document.getElementById('main'));
    // 指定图表的配置项和数据
    var option = {
        title: {
            text: 'ECharts 入门示例'
        },
        tooltip: {},
        legend: {
            data:['销量']
        },
        xAxis: {
            data: ["衬衫","羊毛衫","雪纺衫","裤子","高跟鞋","袜子"]
        },
        yAxis: {},
        series: [{
            name: '销量',
            type: 'bar',
            data: [5, 20, 36, 10, 10, 20]
        }]
    };
    // 使用刚指定的配置项和数据显示图表。
    myChart.setOption(option);
</script>

效果如下:

2.3 查看ECharts官方实例

ECharts提供了很多官方实例,我们可以通过这些官方实例来查看展示效果和使用方法。 官方实例地址:https://www.echartsjs.com/examples/

可以点击具体的一个图形会跳转到编辑页面,编辑页面左侧展示源码(js部分源码),右侧展示图表效果,如下

要查看完整代码可以点击右下角的Download按钮将完整页面下载到本地。 通过官方案例我们可以发现,使用ECharts展示图表效果,关键点在于确定此图表所需的数据格式,然后按照此数据格式提供数据就可以了,我们无须关注效果是如何渲染出来的。 在实际应用中,我们要展示的数据往往存储在数据库中,所以我们可以发送ajax请求获取数据库中的数据并转为图表所需的数据即可。

三、会员数量折线图

会员信息是体检机构的核心数据,其会员数量和增长数量可以反映出机构的部分运营情况。通过折线图可以直观的反映出会员数量的增长趋势。本章节我们需要展示过去一年 时间内每个月的会员总数据量。展示效果如下图:

3.2 完善页面

会员数量折线图对应的页面为/pages/report_member.html。

<!DOCTYPE html>
<html>
    <head>
        <!-- 页面meta -->
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <title>传智健康</title>
        <meta name="description" content="传智健康">
        <meta name="keywords" content="传智健康">
        <meta content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no" name="viewport">
        <!-- 引入样式 -->
        <link rel="stylesheet" href="../css/style.css">
        <script src="../plugins/echarts/echarts.js"></script>
    </head>
    <body class="hold-transition">
        <div id="app">
            <div class="content-header">
                <h1>统计分析<small>会员数量</small></h1>
                <el-breadcrumb separator-class="el-icon-arrow-right" class="breadcrumb">
                    <el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
                    <el-breadcrumb-item>统计分析</el-breadcrumb-item>
                    <el-breadcrumb-item>会员数量</el-breadcrumb-item>
                </el-breadcrumb>
            </div>
            <div class="app-container">
                <div class="box">
                    <!-- 为 ECharts 准备一个具备大小(宽高)的 DOM -->
                    <div id="chart1" style="height:600px;"></div>
                </div>
            </div>
        </div>
    </body>
    <!-- 引入组件库 -->
    <script src="../js/vue.js"></script>
    <script src="../js/axios-0.18.0.js"></script>
    <script type="text/javascript">
        // 基于准备好的dom,初始化echarts实例
        var myChart1 = echarts.init(document.getElementById('chart1'));
        // 使用刚指定的配置项和数据显示图表。
        //myChart.setOption(option);
        axios.get("/report/getMemberReport.do").then((res)=>{
	        myChart1.setOption(
	          {
	                title: {
	                    text: '会员数量'
	                },
	                tooltip: {},
	                legend: {
	                    data:['会员数量']
	                },
	                xAxis: {
	                    data: res.data.data.months//动态数据
	                },
	                yAxis: {
	                    type:'value'
	                },
	                series: [{
	                    name: '会员数量',
	                    type: 'line',
	                    data: res.data.data.memberCount
	                }]
	            });
        });
    </script>
</html>
3.3 导入ECharts库

第一步:将echarts.js文件复制到health_backend工程的plugins目录下
第二步:在report_member.html页面引入echarts.js文件

<script src="../plugins/echarts/echarts.js"></script>
3.4 参照官方实例导入折线图



根据折线图对数据格式的要求,我们发送ajax请求,服务端需要返回如下格式的数据:

3.5 后台代码

1.Controller
在health_backend工程中创建ReportController并提供getMemberReport方法.

@RestController
@RequestMapping("/report")
public class ReportController {

    @Reference
    MemberService memberService;

    @RequestMapping("/getMemberReport")
    public Result getMemberReport(){
        //获取当前日期的日历实例
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.MONTH,-12);//获取当前日期的前12个月的日期
        List<String> list = new ArrayList<>();
        for(int i = 0;i<12;i++){
            calendar.add(Calendar.MONTH,1);
            list.add(new SimpleDateFormat("yyyy-MM").format(calendar.getTime()));
        }
        Map<String,Object> map = new HashMap<>();
        map.put("months",list);
        List<Integer> memberCount = memberService.findMemberCountByMonth(list);
        map.put("memberCount",memberCount);
        return  new Result(true, MessageConstant.GET_MEMBER_NUMBER_REPORT_SUCCESS,map);
    }

    public static void main(String[] args) throws Exception{
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.MONTH,-12);//获取当前日期的前12个月的日期
        String time = DateUtils.parseDate2String(calendar.getTime());
        System.out.println(time);
    }
}

2. 服务接口
在MemberService服务接口中扩展方法findMemberCountByMonth.

List<Integer> findMemberCountByMonth(List<String> list);

3.服务实现类
在MemberServiceImpl服务实现类中实现findMemberCountByMonth方法

@Override
public List<Integer> findMemberCountByMonth(List<String> month) {
    List<Integer> list = new ArrayList<>();
    for(String m : month){
        m = m + "-31";//格式:2019.04.31
        Integer count = memberDao.findMemberCountBeforeDate(m);
        list.add(count);
    }
    return list;
}

4.Dao接口
在MemberDao接口中扩展方法findMemberCountBeforeDate

Integer findMemberCountBeforeDate(String m);

5.Mapper映射文件
在MemberDao.xml映射文件中提供SQL语句

<!--
   根据日期统计会员数,统计指定日期之前的会员数
 -->
 <select id="findMemberCountBeforeDate" parameterType="string"
         resultType="int">
       select count(id) from t_member where regTime &lt;= #{m}
</select>
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值