Bootstrap前端框架
1.概述
Bootstrap 是美国 Twitter 公司的设计师 Mark Otto 和 Jacob Thornton 合作基于 HTML、CSS、JavaScript 开发的简洁、直观、强悍的前端开发框架,使得 Web 开发更加快捷。 Bootstrap 提供了优雅的 HTML 和 CSS 规范,它即是由动态 CSS 语言 Less 写成。(重点是响应式,能适应各种各种设备)
学习方法
参考文档,拷贝案例,修改调整
2.HelloWorld
1、项目中添加 Bootstrap文件目录
2、html页面引入相关的样式和 JS
<link rel="stylesheet" href="/js/bootstrap/css/bootstrap.css">
<script src="/js/jquery/jquery.min.js"></script>
<script src="/js/bootstrap/js/bootstrap.js"></script>
3、添加文档案例查看效果
3.常用组件
面板,栅格系统,列表,表格,按钮,表单,字体图标,模态框
RBAC权限管理系统
1.课程目标
了解权限管理在系统中的作用
熟练使用SSM项目开发环境
熟练使用jQuery完成前台页面的基本功能
完成RBAC权限管理系统的开发
通过该系统熟悉WEB应用的基本开发流程
2.权限管理系统
权限管理,一般指根据系统设置的安全规则或者安全策略,限制用户可以访问而且只能访问自己被授权的资源,从而保障数据资源在合法范围内得以有效使用和管理。权限管理几乎出现在任何系统里面,只要有用户和密码的系统。
在权限管理中使用最多的还是基于角色访问控制(RBAC: Role Based Access Control)
![role.png](https://img.haomeiwen.com/i11635091/e9915db50ff5a889.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)3.RBAC概述
基于角色的访问控制,这种模型的基本概念是把权限(Permission)与角色(Role)联系在一起,用户通过充当合适角色的成员而获得该角色的权限。
在实际的组织中,为了完成组织的业务工作,需要在组织内部设置不同的职位,职位既表示一种业务分工,又表示一种责任与权利。根据业务分工的需要,职位被划分给不同群体,各个群体的人根据其工作任务的需要被赋予不同的职责和权利,每个人有权了解与使用与自己任务相关的信息与资源,对于那些不应该被知道的信息则应该限制他们访问。这就产生了访问控制的需求。
例如,在一间公司中,有老板、经理、行政、人事、财务等不同的职位,在通常情况下,职位所赋予的权利是不变的,但在某个职位上工作的人可以根据需要调整。RBAC 模型对组织内部的这些关系与访问控制要求给出了非常恰当的描述。
RBAC重要对象
-
用户(User):角色施加的主体;用户通过拥有某个或多个角色以得到对应的权限。
-
角色(Role):表示一组权限的集合。
-
权限(Permission):用于限定能够访问的一个资源。
4.需求分析
我们现在的RBAC权限管理系统中包括部门管理,员工管理,权限管理,角色管理 4个模块。
管理流程通常是使用下面的方式完成
1、在角色管理界面,为角色分配权限
2、在员工管理界面,为员工分配角色
在员工通过登录认证后,发起对系统资源的访问时,检查当前员工是否拥有访问对应资源的权限,如果没有,阻止访问,给出提示"您没有权限进行该操作!",反之,放行即可。
每个模块基本都是一些简单的CRUD的需求,所以难度并不大,相对而言,下面几个点可能存在一定的难度:员工与角色的关系管理 , 权限加载 , 角色和权限的关系管理 , 登录会话 , 权限检查
这几个点相对来说存在一些难度,大家一定要细心去分析清楚其中的思路流程。
5.环境搭建
1、 数据库表结构理解, 部门表,员工表,角色表,权限表
2、 创建Maven项目, 项目添加依赖, 以及相关配置文件
3、 重命名包,删除一些之前引入的样式文件与JS 文件
6.部门管理
1、在数据库新建 department 表
2、美化部门页面
拷贝资料中提供的 jsp 和静态资源到指定目录
webapp
--css
--js
--WEB-INF
--views
3、实现分页功能
分析前端twbs-pagination分页插件的使用
1.引入插件
<script src="/js/jquery/jquery.min.js"></script>
<script src="/js/bootstrap/js/bootstrap.js"></script>
<script src="/js/plugins/twbsPagination/jquery.twbsPagination.min.js"></script>
2.页面中添加ul元素
<div style="text-align: center;">
<ul id="pagination" class="pagination"></ul>
</div>
3.在表单中添加隐藏的input当前页
<input type="hidden" name="currentPage" id="currentPage" value="1">
4.编写分页js代码
$("#pagination").twbsPagination({
totalPages: ${result.totalPage},
startPage: ${result.currentPage},
visiblePages:5,
first:"首页",
prev:"上页",
next:"下页",
last:"尾页",
onPageClick:function(event,page){ //点击页码的时候会执行的方法
$("#currentPage").val(page);
$("#searchForm").submit();
}
});
7.员工管理
1.参考部门代码实现员工分页查询
2.关键字以及部门下拉框高级查询
3.新增员工功能
4.编辑员工功能
隐藏密码输入框
<c:if test="${empty employee}"> </c:if>
部门回显
<div class="form-group">
<label for="dept" class="col-sm-2 control-label">部门:</label>
<div class="col-sm-6">
<select class="form-control" id="dept" name="dept.id">
<c:forEach items="${depts}" var="d">
<option value="${d.id}">${d.name}</option>
</c:forEach>
</select>
<script>
$("#dept").val(${employee.dept.id})
</script>
</div>
</div>
8.角色管理
1.创建角色表, 员工角色关系管理中间表
2.实现角色页面的crud功能
3.员工页面选择角色功能
角色多选框列表显示
model.addAttribute("roles",roleService.listAll());
<select multiple class="form-control allRoles" size="15">
<c:forEach items="${roles}" var="r" >
<option value="${r.id}">${r.name}</option>
</c:forEach>
</select>
角色左移右移效果
function moveSelected(src, target) {
$("."+target).append($("."+src+" option:selected"));
}
function moveAll(src, target) {
$("."+target).append($("."+src+" option"));
}
选择超管后隐藏角色区域
var roleDiv;
$("#admin").click(function () {
//判断是否是勾选状态
var checked = $(this).prop('checked');
if(checked){
//删除角色的div
roleDiv = $("#role").detach();
}else{
//恢复角色div,加到超管的后面
$("#adminDiv").after(roleDiv);
}
})
编辑时若员工是超管也需要隐藏角色区域
var checked = $("#admin").prop('checked');
if(checked){
//删除角色的div
roleDiv = $("#role").detach();
}
4.保存员工时处理角色
注意:下拉框中选中的数据的才会提交,若没有选中的数据是不会提交的。
解决: 修改按钮为普通按钮,绑定点击事件处理函数,在函数中把右边的select 元素中的 option 设置为选中的,再提交表单。
$("#submitBtn").click(function () {
//把右边的下拉框的option全部选择
$(".selfRoles option").prop("selected",true);
//提交表单
$("#editForm").submit();
})
新增时后台需要处理中间表
if(ids!=null&&ids.length>0){
for (Long roleId : ids) {
employeeMapper.insertRelation(employee.getId(),roleId);
}
}
编辑时先删除关系再处理中间表
employeeMapper.deleteRelation(employee.getId());
sql处理
<delete id="deleteRelation">
delete from employee_role where employee_id = #{eid}
</delete>
<insert id="insertRelation">
insert into employee_role (employee_id,role_id) VALUES (#{eid},#{rid})
</insert>
5.角色回显
员工类中添加roles集合
private List<Role> roles = new ArrayList<>();
使用额外sql查询员工的角色信息
<collection property="roles" select="cn.wolfcode.rbac.mapper.RoleMapper.selectByEmpId" column="id" ></collection>
<select id="selectByEmpId" resultMap="BaseResultMap">
select r.id,r.name,r.sn
from role r JOIN employee_role er
ON r.id = er.role_id
where er.employee_id = #{eid}
</select>
页面循环获取
<select multiple class="form-control selfRoles" name="ids">
<c:forEach items="${employee.roles}" var="r" >
<option value="${r.id}">${r.name}</option>
</c:forEach>
</select>
6.角色去重
注意:角色回显的时候,发现左右两边的角色有重复,应该是有右边有的角色,不应该在左边出现。
解决:页面加载完,拿左边两边的option 对比,遍历左边每个角色,若已经在右边列表内,则需要删除。
//1.把已有的角色id放入一个数组中(右边)
var ids = [];
$(".selfRoles option").each(function (i, ele) {
ids.push( $(ele).val());
})
//2.遍历所有的角色(左边)
$(".allRoles option").each(function (i, ele) {
//3.判断是否存在ids数组中,如果是就删除掉自己
var id = $(ele).val();
if($.inArray(id,ids)!=-1){
$(ele).remove();
}
})
7.员工删除
删除员工数据,还要从中间表employee_role 中删除与此员工相关的数据。
employeeMapper.deleteRelation(employee.getId());
9.权限管理
权限表要包含什么字段?
权限名称是给分配权限的人看的,用中文,见名知意。
权限表达式要唯一这样才能区分用户访问的到底是什么资源。要做访问的资源的权限限制,其实就是对系统中的动态资源或者说控制器中的处理方法做限制,因为处理方法包含对数据库的CRUD 操作。换句话说,控制器中的处理方法就是一个一个权限,即数据库中权限表的数据来源于所有控制器的一个一个处理方法。权限表达式的值需要具有唯一性, 那么我们就约定权限表达式组成:控制器类名首字母小写除去Controller:方法名(department:list),这样就可以唯一了。
控制器中每个处理方法怎么转化成权限表中的数据?
一条条手动添加太麻烦,需要用代码来批量添加
我们可以自定义一个注解 , 在处理方法上贴该注解,注解的值使用中文名称即可,表明是什么样的权限 , 再利用贴了注解的方法 , 生成唯一的权限表达式。
贴注解了除了上面的好处,还可以区分一个处理方法是否要做权限限制,贴了代表要限制,反之不要。
实现权限管理页面
1.权限管理页面的列表显示
2.权限加载实现步骤
为权限页面的加载权限按钮绑定点击事件 , 点击按钮后发送ajax到后台
$(".btn_reload").click(function () {
$.get('/permission/reload.do',function (data) {
if(data.success){
window.location.reload(); //重新加载当前页面
}else{
alert(data.msg)
}
})
})
编写controller处理方法
@RequestMapping("/reload")
@ResponseBody
public JsonResult reload() {
try {
permissionService.reload();
return new JsonResult();
} catch (Exception e) {
e.printStackTrace();
return new JsonResult(false,"加载失败");
}
}
自定义权限注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiredPermission {
String value();
String expression();
}
在控制器方法上贴权限注解
@RequiredPermission(value="部门列表",expression="department:list")
@RequestMapping("/list")
public String list(Model model, @ModelAttribute("qo") QueryObject qo){
PageResult<Department> pageResult = departmentService.query(qo);
model.addAttribute("pageResult", pageResult);
return "department/list";
}
实现加载权限的逻辑
实现步骤: 首先获取容器对象,再从容器获取所有贴有 @Controller 注解的 bean,再获取里面贴有自己自定义的注解的方法,获取权限名称和权限表达式(可以手动拼接,亦可使用注解定义获取),判断该方法是否贴有我们自定义注解,还要判断这个方法拼接权限表达式是否存放在数据库中,若贴有注解且该权限表达式不存在数据库, 则创建 Permission对象,封装数据并存入数据库中。
@Autowired
private ApplicationContext ctx;
public void reload() {
//获取所有的权限表达式
List<String> permissions = permissionMapper.selectAllExpression();
//把所有的controller中贴了权限注解的方法,转换成权限对象,并保存到数据库中
//利用spring上下文,获取带有controller注解的所有bean
Map<String, Object> map = ctx.getBeansWithAnnotation(Controller.class);
Collection<Object> values = map.values();
//遍历每个controller,获取字节码对象,再获取该字节码中所有的方法
for (Object controller : values) {
Class<?> clazz = controller.getClass(); //字节码对象
Method[] methods = clazz.getDeclaredMethods(); //该字节码中所有的方法
//遍历每个方法
for (Method method : methods) {
//判断方法上是否贴有权限注解
RequiredPermission annotation = method.getAnnotation(RequiredPermission.class);
//判断注解是否为空
if(annotation!=null){
//获取注解中的权限名称name
String name = annotation.value();
//获取注解中的权限表达式
String expression = annotation.expression();
//或者通过反射获取权限表达式
//String simpleName = clazz.getSimpleName().replace("Controller","");
//uncapitalize方法可以把首字母变为小写
// String expression = StringUtils.uncapitalize(simpleName)+":"+method.getName();
//如果没有存在数据库,就插入进入
if(!permissions.contains(expression)) {
//封装成权限对象
Permission permission = new Permission();
permission.setName(name);
permission.setExpression(expression);
//把权限对象保存到数据库
permissionMapper.insert(permission);
}
}
}
}
}
5.新增/编辑/删除角色时处理权限数据
参考员工管理角色时的实现步骤
10.用户登录
1.使用ajax方式的提交登录
用户体验更好,保留用户刚输入的用户名和密码,失败之后不需要跳转页面可马上提示错误信息。
$(".submitBtn").click(function () {
//serialize方法可以把表单的所有参数都获取出来,使用&拼接
$.post('/login.do',$("#loginForm").serialize(),function (data) {
if(data.success){
window.location.href = "/employee/list.do";
}else{
alert(data.msg)
}
})
})
后台处理登录逻辑
@RequestMapping("/login")
@ResponseBody
public JsonResult login(String username, String password) {
JsonResult json = new JsonResult();
try {
employeeService.login(username, password);
return new JsonResult();
} catch (Exception e) {
e.printStackTrace();
return new JsonResult(false,"用户名或密码错误");
}
}
业务层
public void login(String username, String password) {
//查询数据库中是否有能匹配上的数据
Employee employee = employeeMapper.selectByUsernameAndPassword(username, password);
if (employee == null) { //登录失败
//通知调用者这里失败了
throw new RuntimeException("账号和密码不匹配");
}
//登录成功,把当前登录成功的对象存入session中,供后期登录校验;
session.setAttribute("EMPLOYEE_IN_SESSION", employee);
}
登录后页面可显示用户昵称
<span class="hidden-xs">${EMPLOYEE_IN_SESSION.name}</span>
登录拦截器
public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler)
throws Exception {
//获取当前登录的用户
Employee employee = session.getAttribute("EMPLOYEE_IN_SESSION");
if (employee == null) { //没有登录,不放行
resp.sendRedirect("/login.html"); //回到登录界面
return false;
}
return true; //已经登录,放行
}
把拦截器配置到mvc.xml文件中
<!-- 注册拦截器 -->
<mvc:interceptors>
<!-- 配置登录拦截器 -->
<mvc:interceptor>
<!-- 对哪些资源起拦截作用 -->
<mvc:mapping path="/**"/>
<!-- 对哪些资源不起拦截作用 -->
<mvc:exclude-mapping path="/login.do"/>
<!-- 哪个Bean是拦截器 -->
<bean class="cn.wolfcode.rbac.web.interceptor.CheckLoginInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
11.权限控制
权限拦截流程
1.添加权限拦截器
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//如果登录用户是管理员,直接放行
Employee employee = session.getAttribute("EMPLOYEE_IN_SESSION");
if(employee.isAdmin()){
return true;
}
//如果不是管理员,获取当前要执行的控制器的处理方法
HandlerMethod handlerMethod = (HandlerMethod)handler;
//判断method中是否贴了权限注解@RequiredPermission
RequiredPermission annotation = handlerMethod.getMethodAnnotation(RequiredPermission.class);
if(annotation==null){
//如果没有贴,直接放行
return true;
}
//通过权限注解获取权限表达式
String expression = annotation.expression();
//通过反射来获取权限表达式
///String simpleName = handlerMethod.getBean().getClass().getSimpleName().replace("Controller","");
// String expression = StringUtils.uncapitalize(simpleName)+":"+handlerMethod.getMethod().getName();
//获取当前登录用户拥有的权限List<String>
List<String> permissions = permissionService.selectByEmpId(employee.getId());
if(permissions.contains(expression)){
return true;//放行
}
//跳转到没权限的提示页面
request.getRequestDispatcher("/WEB-INF/views/common/nopermission.jsp").forward(request,response);
return false; //不放行
}
2.提供查询权限的方法
<select id="selectByEmpId" resultType="String">
select distinct p.expression from permission p
join role_permission rp on p.id = rp.permission_id
join employee_role er on rp.role_id = er.role_id
where er.employee_id = #{id}
</select>
3.把拦截器配置到mvc.xml文件中
<!-- 注册拦截器 -->
<mvc:interceptors>
<!-- 配置登录拦截器 -->
<mvc:interceptor>
<!-- 对哪些资源起拦截作用 -->
<mvc:mapping path="/**"/>
<!-- 对哪些资源不起拦截作用 -->
<mvc:exclude-mapping path="/login.do"/>
<!-- 哪个Bean是拦截器 -->
<bean class="cn.wolfcode.rbac.web.interceptor.CheckPermissionInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
4.每次判断权限都要查询数据库 , 效率太低
权限数据不会经常变动 , 不用每次去数据库查询权限数据, 可以在登录时就把用户的权限存入session中
if(!employee.isAdmin()){ //如果不是管理员,就需要查询权限信息
List<String> permissions = permissionService.selectByEmpId(employee.getId());
session.setAttribute("EXPRESSION_IN_SESSION", permissions);
}
5.抽取UserContext工具 , 方便获取数据
可以在代码任意的地方获取请求对象,或者 HttpSessison 对象,也可以避免操作 session 时,key 的名称过长易导致写错。
public abstract class UserContext {
public static final String EMPLOYEE_IN_SESSION = "EMPLOYEE_IN_SESSION";
public static final String EXPRESSION_IN_SESSION = "EXPRESSION_IN_SESSION";
//往session存入登录用户
public static void setCurrentUser(Employee emp) {
getSession().setAttribute(EMPLOYEE_IN_SESSION, emp);
}
//从session获取登录用户
public static Employee getCurrentUser() {
return (Employee) getSession().getAttribute(EMPLOYEE_IN_SESSION);
}
//获取session对象
public static HttpSession getSession() {
ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
return attrs.getRequest().getSession();
}
//往session存入登录用户的权限信息
public static void setExpression(List<String> permissions) {
getSession().setAttribute(EXPRESSION_IN_SESSION,permissions);
}
//从session获取登录用户的权限信息
public static List<String> getExpression() {
return (List<String>) getSession().getAttribute(EXPRESSION_IN_SESSION);
}
}
![rbac.png](https://img.haomeiwen.com/i11635091/f4365a0158a740b3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
网友评论