EasyUI前端应用基础续编。
datagrid网格之模糊查询与数据表冻结
模糊查询:
这里以学生姓名stuname作模糊查询,where 1=1 and stuname='%xxx%' 注:1=1表示所有。
以上例为基础:div作为table的一个工具,div里有两个输入框,一个链接按钮,分别用于用户输入学生名,与学号作对学生的模糊查询。
一。jsp
<table id="dg"></table>
<div id="tb" style="height:45px;line-height:45px;">
学生ID:<div id="search_id" class="easyui-numberbox"></div>
学生姓名:<div id="search_stuname" class="easyui-textbox"></div>
<a href="#" class="easyui-linkbutton" data-options="iconCls:'icon-search'" id ="search_btn">搜索</a>
</div>
二。js 注意:url:''/sshwebtest/getAllPagerStudents'这是链接到后台的控制层'
$(function(){
$("#dg").datagrid({
title:"用户信息表",
//url:"/sshwebtest/getAllStudentsD", //没有分页显示的远程后台数据处理
url:"/sshwebtest/getAllPagerStudents",
pagination:true, //分页显示,有修改每页显示多少条功能,不用写代码
pageSize:5, //初始化每页显示几条记录
pageList:[5,10,25,30,100], //设置分页每页能显示多少条记录选择项,这里是每页有5条,10条,,,多个选项,
// method:"post", //默认为post可以不写这行代码
columns:[[
{field:"ck",checkbox:"true"}, //最左侧有复选框,可供我们选择,
{field:"id",title:"id",width:100},
{field:"stuname",title:"stuname",width:100},
{field:"password",title:"password",width:100},
{field:"state",title:"state",width:100,formatter:function(value,row,index){
if(value == 1){
return "正常";
}else{
return "禁用状态";
}
}},
{field:"regDate",title:"regDate",width:100},
{field:"height",title:"height",width:100}
]], //两个中括号,表示二维表格
striped:true, //把表格网纹化,即奇偶行不同背影 色
toolbar:"#tb",
toolbar:[ //网格上有工具栏,有一个工具(铅笔)
{
text:"编辑",
iconCls:"icon-edit",
handler:function(){
//alert("单击");
var datas = $("#dg").datagrid("getChecked"); //获取选中复选框中记录
if(datas.length > 0 ){
for(var i=0;i<datas.length;i++)
console.log(datas[i]);
}
}
}
]
});
$("#search_btn").click(function(){ //链接按钮的单击事件
var stuid = $("#search_id").numberbox("getValue");
var stuname = $("#search_stuname").textbox("getValue");
var queryParam = $("#dg").datagrid("options").queryParams;
queryParam.id = stuid;
queryParam.stuname = stuname;
$("#dg").datagrid("load"); //加载
});
});
三.后台控制层StuCtroller.java与service,dao的一个代码片段
//带分页显示所有学生信息,学生姓名作模糊查询,不用注入对象,这里我们直接用网页与easyui交互,用到json格式数据,所以用@ResponseBody注解
@Controller
public class StuController {
@Autowired
private StudentService stuService;
@ResponseBody
@RequestMapping(value="/getAllPagerStudents",method=RequestMethod.POST)
public Pager<Student> getAllPagerStudents(Integer page,
Integer rows, //page,rows值由easyui 网格自动发送的请求而过来的
@RequestParam(value="id",required=false) Integer id,
@RequestParam(value="stuname",required=false) String stuname) //id,stuname是接收输入框输入的数据
{
System.out.println("StuController.java中由easyui 网格发送的page,rows "+page+","+rows);
if(page != null && page>0) SystemContext.setPageSize(rows); //每一页两条记录
if(rows !=null && rows>0) SystemContext.setPageOffset((page-1)*rows); //从第一条记录开始分
Student stu = new Student();
if(id!=null && id >0 ) stu.setId(id);
if(stuname !=null && !"".equals(stuname) ) stu.setStuname(stuname);
Pager<Student> pager = stuService.getAllPagerStus(stu);
SystemContext.removePageOffset();
SystemContext.removePageSize();
return pager;
}
}
-----------------------------------------------------StudentServiceImpl--------------------------------------------
@Service("stuService")
public class StudentServiceImpl extends BaseServiceIpml<Student> implements StudentService {
@Override
public Pager<Student> getAllPagerStus(Student stu) {
return stuDao.getAllPagerStus(stu);
}
}
------------------------------------------------------StudentService---------------------------------------
public interface StudentService extends BaseService<Student> {
/**
* 获取所有学生信息,不支持分页
* @return
*/
public List<Student> getAllStus();
/**
* 获取所有学生信息,支持分页
* @param stu
* @return
*/
public Pager<Student> getAllPagerStus(Student stu);
}
-----------------------------------------------------------StudentDaoImpl.java------------------------------
@Repository("stuDao")
public class StudentDaoImpl extends BaseDaoImpl<Student> implements StudentDao{
//下面写的方法都是本类个性化的方法
@Override
public List<Student> getAllStus() {
String hql="from Student";
return super.list(hql, null, null);
}
//分页且模糊查询
@Override
public Pager<Student> getAllPagerStus(Student stu) {
String hql="from Student s where 1=1";
if(stu.getId()>0) hql+=" and u.id="+stu.getId();
if(stu.getStuname()!=null &&!"".equals(stu.getStuname()))
hql+=" and s.stuname like'%"+stu.getStuname()+"%'";
return super.find(hql, null, null);
}
}
--------------------StudentDao.java-------------------------------------
public interface StudentDao extends BaseDao<Student>{
/**
* 获取所有学生信息,不支持分页
*/
public List<Student> getAllStus();
/**
* 获取所有学生信息,支持分页
* @param stu
* @return
*/
public Pager<Student> getAllPagerStus(Student stu);
}
---------------------------------------------Pager.java--------------------------------------------------------
public class Pager<T> {
/**
* 分页的大小,每页显示数据的条数
*/
private int size;
// 每页的第一条数据的,条数索引
private int offset;
/**
* 查询出来的总的数据条数,rows--与easyui分页插件里变量名称一致
*/
private long total;
/**
* 每张每页是具体数据(一页所有的记录),rows--easyui与分页插件里变量名称一致
*/
private List<T> rows;
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public int getOffset() {
return offset;
}
public void setOffset(int offset) {
this.offset = offset;
}
public long getTotal() {
return total;
}
public void setTotal(long total) {
this.total = total;
}
public List<T> getRows() {
return rows;
}
public void setRows(List<T> rows) {
this.rows = rows;
}
@Override
public String toString() {
return "Pager [size=" + size + ", offset=" + offset + ", total=" + total + ", rows=" + rows + "]";
}
}
-------------------SystemContext.java--------------------------------
/**
* ThreadLocal:数据共享,本地线程变量里的数据,每个线程中创建副本,在多条线程中,给这个变量赋值,
* 每条线程使用不会冲突 进行分页信息通信
* 排序信息通信
*
* @author Administrator
*
*/
public class SystemContext {
// 分页大小,每页显示的数据条数
private static ThreadLocal<Integer> pageSize = new ThreadLocal<>();
// 每页的起始的条数索引
private static ThreadLocal<Integer> pageOffset = new ThreadLocal<>();
// 排序的字段
private static ThreadLocal<String> sort = new ThreadLocal<>();
// 排序方式
private static ThreadLocal<String> order = new ThreadLocal<>();
public static Integer getPageSize() {
return pageSize.get();
}
public static void setPageSize(Integer _pageSize) {
pageSize.set(_pageSize);
}
public static Integer getPageOffset() {
return pageOffset.get();
}
public static void setPageOffset(Integer _pageOffset) {
pageOffset.set(_pageOffset);
}
public static String getSort() {
return sort.get();
}
public static void setSort(String _sort) {
sort.set(_sort);
}
public static String getOrder() {
return order.get();
}
public static void setOrder(String _order) {
order.set(_order);
}
public static void removePageSize() {
pageSize.remove();
}
public static void removePageOffset() {
pageOffset.remove();
}
public static void removeSort() {
sort.remove();
}
public static void removeOrder() {
order.remove();
}
}
------------------------------------------------------------------------BaseService.java--------------------
public interface BaseService<T> {
// 添加
public T add(T t);
// 删除
public void delete(int id);
// 修改
public void update(T t);
// 查询
public T load(int id);
}
----------------------------------------------------------BaseServiceImpl.java-------------------------------
public class BaseServiceIpml<T> implements BaseService<T> {
@Autowired
protected BaseDao<T> baseDao;
@Override
public T add(T t) {
return baseDao.add(t);
}
@Override
public void delete(int id) {
baseDao.delete(id);
}
@Override
public void update(T t) {
baseDao.update(t);
}
@Override
public T load(int id) {
return baseDao.load(id);
}
}
------------------------------------------------------------BaseDao.java接口--------------------------------
public interface BaseDao<T> {
// 添加
public T add(T t);
// 删除
public void delete(int id);
// 修改
public void update(T t);
// 查询
public T load(int id);
}
-----------------------------------------------------------BaseDaoImpl.java------------------------------
/**
* BaseDaoImpl:操作数据库的基本方法代码
*
* @author Administrator
* @param <T>
*/
public class BaseDaoImpl<T> implements BaseDao<T> {
@Autowired
private SessionFactory sessionFactory; //spring.xml配置了<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
private Class<?> clazz;
public Class<?> getClazz() {
/*
Type type= this.getClass().getGenericSuperclass();//this将代表BaseDaoImpl的子类对象如:UserDaoImpl extends BaseDaoImpl<User>
ParameterizedType pt=(ParameterizedType) type;
Type[] types = pt.getActualTypeArguments();
Class<?> clz = (Class<?>) types[0];
return clz;
*/
if (clazz == null) {
clazz = ((Class<?>) ((((ParameterizedType) (this.getClass().getGenericSuperclass()))
.getActualTypeArguments())[0]));
}
return clazz;
}
/**
* 获取数据连接回话的方法
*/
public Session getSession() {
return sessionFactory.getCurrentSession();
}
@Override
public T add(T t) {
getSession().save(t);
return t;
}
@Override
public void delete(int id) {
getSession().delete(load(id));
}
@Override
public void update(T t) {
getSession().update(t);
}
@SuppressWarnings("unchecked")
@Override
public T load(int id) {
return (T) getSession().load(getClazz(), id);//hibernate中有load(xxx.class,xxx)方法加载数据库数据即返回记录
}
/**
* 用hql语句查询多条记录,没有分页,list返回对象.两种spring处理数据库的占位符
* @param hql
* @param objs 替换hql语句中?占位符的实参
* @param alias 替换hql语句中:name占位符的实参
* @return
*/
//其它类如UserDaoImpl,StudentDaoImpl等调用如 :return super.list(hql, null, null);
@SuppressWarnings("unchecked")
public List<T> list(String hql, Object[] objs, Map<String, Object> alias) {
hql = initSort(hql); //初始化排序规则
Query<T> query = getSession().createQuery(hql);
setParameter(query,objs); //将hql里?占位符替换
setAliasParameter(query,alias);
return query.list();
}
/**
* 替换hql语句中:name占位符的实参
* @param query
* @param alias
*/
@SuppressWarnings("rawtypes")
private void setAliasParameter(Query<T> query, Map<String, Object> alias) {
if(alias != null) {
Set<String> keys = alias.keySet();
for(String key:keys) {
Object val = alias.get(key); //取key对应的值
if(val instanceof Collection) { //判断是不是一个集合
query.setParameterList(key, (Collection)val);
}else {
query.setParameter(key, val);
}
}
}
}
/**
* 将hql里?占位符替换
* @param query
* @param objs
*/
private void setParameter(Query<T> query,Object[] objs) {
if(objs != null && objs.length > 0) {
int index = 0;
for(Object obj:objs) {
query.setParameter(index++, obj);
}
}
}
/**
* 给hql语句加排序规则
* @param hql
* @return
*/
private String initSort(String hql) {
String sort = SystemContext.getSort(); // 数据我们可以在Controller放
String order = SystemContext.getOrder();
if (sort != null && !"".equals(sort.trim())) {
hql += " order by " + sort; //例:String hql="select from User order by desc...."
if (!"desc".equals(order)) {
hql += " asc";
} else {
hql += " desc";
}
}
return hql;
}
/**
* 查询多条数据,支持分页
* @param hql
* @param objs
* @param alias
* @return
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public Pager<T> find(String hql, Object[] objs, Map<String, Object> alias){
hql = initSort(hql);//初始化排序规则
Query<T> query = getSession().createQuery(hql);
setParameter(query,objs);//将hql里?占位符替换
setAliasParameter(query,alias);
Pager<T> pager = new Pager<>();
setPager(query,pager);
List<T> datas = query.list();
pager.setRows(datas);
//还需要一个查询的中条数 select count(*) from users where id>10
String countHql = getCountHql(hql);
Query countQuery = getSession().createQuery(countHql);
setParameter(countQuery,objs);//将hql里?占位符替换
setAliasParameter(countQuery,alias);
long total = (long) countQuery.uniqueResult();
pager.setTotal(total);
return pager;
}
private String getCountHql(String hql) {
String hhql = hql.substring(hql.indexOf("from")); // 拿到hql语句的form开始后半部分:from users where id>10
String countHql = "select count(*) " + hhql;
//hql语句,fetch
countHql = countHql.replace("fetch", "");
return countHql;
}
private void setPager(Query<T> query, Pager<T> pager) {
Integer pageSize = SystemContext.getPageSize();
Integer pageOffset = SystemContext.getPageOffset();
if(pageOffset == null || pageOffset < 0) {
pageOffset = 0; // pageOffset的默认值
}
if (pageSize == null || pageSize < 0) {
pageSize = 10; // 没有设置每页大小,默认每页显示10条
}
pager.setOffset(pageOffset);
pager.setSize(pageSize);
// data数据,也就是分页的数据 select id,username,pasword from users where id>10; 一张页面上显示的数据
query.setFirstResult(pageOffset).setMaxResults(pageSize);
}
//使用sql语句查询的方法,list,pager
/**
* 针对一些特殊的查询,返回不受泛型的制约,返回object
* @param hql
* @param objs
* @param alias
* @return
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public Object queryByHql(String hql, Object[] objs, Map<String, Object> alias) {
Query query = getSession().createQuery(hql);
setParameter(query,objs);//将hql里?占位符替换
setAliasParameter(query,alias);
return query.uniqueResult();
}
/**
* 应对某些特殊情况要用hql语句,来做更新
* @param hql
* @param objs
* @param alias
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public void updateByHql(String hql, Object[] objs, Map<String, Object> alias) {
Query query = getSession().createQuery(hql);
setParameter(query,objs);//将hql里?占位符替换
setAliasParameter(query,alias);
query.executeUpdate();
}
}
四,DAO层,service层见代码 'sshwebtest'项目
五,测试
http://localhost/sshwebtest/

数据表冻结
项目中,显示的数据字段数很多,屏幕宽显示不小,这时,就会生成滚动条进行滚动查看:有些列和行,我们不希望它随着滚动而被遮盖,这个时候我们就可以使用冻结功能:
1.冻结列:
frozenColumns array 和列( column )属性一样,但是这些列将被冻结在左边
应用格式:frozenColumns:[[]]
例:冻结第一到第三列:id,password,stuname
$("#dg").datagrid({
width:500, //网格的宽,设置小一点可以看出移动滚动条的效果
height:300,
title:"用户信息表",
//url:"/sshwebtest/getAllStudentsD", //没有分页显示的远程后台数据处理
url:"/sshwebtest/getAllPagerStudents",
pagination:true, //分页显示,有修改每页显示多少条功能,不用写代码
pageSize:5, //初始化每页显示几条记录
pageList:[5,10,25,30,100], //设置分页每页能显示多少条记录选择项,这里是每页有5条,10条,,,多个选项,
frozenColumns:[[
{field:"ck",checkbox:"true"}, //最左侧有复选框,可供我们选择,
{field:"id",title:"id",width:100},
{field:"stuname",title:"stuname",width:100},
{field:"password",title:"password",width:100},
]],
columns:[[ //列数组
{field:"state",title:"state",width:100,formatter:function(value,row,index){
if(value == 1){
return "正常";
}else{
return "禁用状态";
}
}},
{field:"regDate",title:"regDate",width:100},
{field:"height",title:"height",width:100}
]], //两个中括号,表示二维表格
striped:true,
…………………………其它代码与上面一样

2.冻结行:
当数据行很多时,会呈现垂直的滚动条,但是向下滚动,表头却被淹没了,这不符
合人类偷懒的特点(我怎么可能记得住每列的数据代表的意思!),所以需要固定表头。
21.fit: true, // 单独 fit 固定表头,
注:固定了表头,只显示第一页,显分页栏不见了,冻结的列也失效
22.onLoadSuccess: function(){
$(this).datagrid("freezeRow",0); // 额外固定某些行
}
--------
onLoadSuccess: function(){
$(this).datagrid("freezeRow",0); // 额外固定某些行(0表示第一行)
},
frozenColumns:[[
{field:"ck",checkbox:"true"}, //最左侧有复选框,可供我们选择,
{field:"id",title:"id",width:100},
{field:"stuname",title:"stuname",width:100},
{field:"password",title:"password",width:100},
]],
//fit:true,
columns:[[ //列数组
{field:"state",title:"state",width:100,formatter:function(value,row,index){
if(value == 1){
return "正常";
}else{
return "禁用状态";
}
}},
{field:"regDate",title:"regDate",width:100},
{field:"height",title:"height",width:100}
]],
-----------------
网格可编辑保存数据到数据库中。
- 大概功能代码editor:点击一行,显示可编辑框,上一次点的行处于不可编辑状态
------
columns:[[ //列数组
{field:"ck",checkbox:"true"}, //最左侧有复选框,可供我们选择,
{field:"id",title:"id",width:100},
{field:"stuname",title:"stuname",width:100,editor:{type:"textbox"}},
{field:"password",title:"password",width:100},
{field:"state",title:"state",width:100,formatter:function(value,row,index){
if(value == 1){
return "正常";
}else{
return "禁用状态";
}
},editor:{type:"textbox"}},
{field:"regDate",title:"regDate",width:100,editor:{type:"datebox"}},
{field:"height",title:"height",width:100,editor:{type:"numberbox",options:{precision:2}}},//precision 编辑后保留两位小数
{field:"roles",title:"roles",width:100,formatter:function(value,row,index)
{
//这里只是举例讲,只列一个角色权根
var roleNameStr="";
console.log(value); //[{0...}] [{0...}] [{0...}]从浏览器控制台F12中可以看出value是一个二维数组,而记录都存在第二维的0下标上的一维数组中
if(value !=null && value.length >0)
roleNameStr = value[0].roleName;
return roleNameStr;
},editor:{type:"combobox",options:{url:"/sshwebtest/getRoles",method:"post",valueField:"id",textField:"roleName",required:true,panelHeight:160}
}}
]], //两个中括号,表示二维表格
onClickRow:function(index){ //可编辑行单击事件,此处用于打开可编辑网格功能.index为点击的行索引
if(editIndex !=index){ //默认情况下,点击某一行,它可编辑,但是我们再点一行时,上一点击行仍处于可编辑,这可不好,
$("#dg").datagrid("rejectChanges")
}
$("#dg").datagrid("beginEdit",index);
$("#dg").datagrid("selectRow",index); //选中某一行后,左则的复选框打上勾
editIndex = index;
},
});
var editIndex;

- 编辑后保存到数据库,取消编辑。
1.jsp中添加两个链接按钮 在html中表示空格。
<div id="tb" style="height:45px;line-height:45px;">
<a id="save_stu" data-options="iconCls:'icon-save',plain:true" class="easyui-linkbutton">保存</a>
<a id="undo_stu" data-options="iconCls:'icon-undo',plain:true" class="easyui-linkbutton">取消</a>
。。。。。。。
2.js中添加两个链接按钮的单击事件
//保存修改的记录
$("#save_stu").click(function(){
//添加到数据库之前,数据有效性检验
//根据editIndex拿到这个row对象
var row = $("#dg").datagrid("getRows")[editIndex];
//根据editIndex,拿到行的编辑器,简单类型的字段,直接获取值,roles是Role对象类型,单独处理
var editor = $("#dg").datagrid("getEditor",{index:editIndex,field:"roles"});
var roleid = $(editor.target).combobox("getValue"); //获取角色的编号
var roletext = $(editor.target).combobox("getText"); //获取角色的描述文本
//让datagrid接受修改,并通ajax发出修改请求
$("#dg").datagrid("acceptChanges");
$.ajax({
url:"/sshwebtest/updateStudent",
method:"post",
//如果说Student对象简单,属性没有复杂的关联性,但我们Student中有set集合role对象,springmvc不能解析,所以必须声明请求的数据类型为json数据格式
contentType:"application/json;charset=utf-8",
//传过去的数据格式,从js的对象格式转化为json格式,stringif()可以将java对象转化为json
data:JSON.stringify({ //以下字段名要与Student对象的成员变量名相同
id:row.id,
stuname:row.stuname,
password:row.password,
state:row.state,
regDate:row.regDate,
height:row.height,
roles:[{id:roleid,roleName:roletext}] //[]表示集合
}),
success:function(rs){ //如呆请求成功,会发回来一个对象rs(rs随便起名,但一定要写在小括号中)
if(rs == "success"){
editIndex = undefined;
$("#dg").datagrid("reload");
}else{
alert("修改失败!");
}
}
})
});
//取消编辑,很简单,只要上的面拒绝修改代码即可
$("#undo_stu").click(function(){
$("#dg").datagrid("rejectChanges");
editIndex = undefined;
})
3.后台响应前台的$.ajx()请求 StuController.java
基Role.java,RoleDao.java RoleDaoImpl.java,RoleService.java,RoleServiceImpl.java见项目 'sshwebtest'
@ResponseBody //返回json格式到网页中
@RequestMapping(value="/updateStudent", method=RequestMethod.POST)
public String updateStudent(@RequestBody Student stu) { //把接收的参数转为json格式数据
stuService.update(stu);
return "success";
}
4.测试:
首先:给表sb_roles写入几个记录,如1-超级管理员 2-一般管理员……。
sb_student_role写入几个记录 1 1,2 1, 即每个学生对应自已的权限
其次:http://localhost/sshwebtest 回车,

基于权限控制模型实现学生/用户角色与权设置
1权限控制模型
----我们在做任何一款产品的时候,或多或少都会涉及到用户和权限的问题。譬如,做企业类软件,不同部门、不同职位的人的权限是不同的;做论坛类产品的时候,版主和访客权限也是不一样的;再例如一款产品的收费用户和免费用户权限也是迥然不同的。
----我们去参照已有的比较成熟的权限模型:RBAC(Role-Based Access Control)——基于角色的访问控制。以产品经理的角度去解析RBAC模型,并分别举例如何运用这套已得到验证的成熟模型。
-
RBAC模型是什么?
RBAC是一套成熟的权限模型。在传统权限模型中,我们直接把权限赋予用户。而在RBAC中,增加了“角色”的概念,我们首先把权限赋予角色,再把角色赋予用户。这样,由于增加了角色,授权会更加灵活方便。在RBAC中,根据权限的复杂程度,又可分为RBAC0、RBAC1、RBAC2、RBAC3。其中,RBAC0是基础,RBAC1、RBAC2、RBAC3都是以RBAC0为基础的升级。我们可以根据自家产品权限的复杂程度,选取适合的权限模型。
image.png - 基本模型RBAC0
解析:
RBAC0是基础,很多产品只需基于RBAC0就可以搭建权限模型了。在这个模型中,我们把权限赋予角色,再把角色赋予用户。用户和角色,角色和权限都是多对多的关系。用户拥有的权限等于他所有的角色持有权限之和。
image.png
举例:
譬如我们做一款企业管理产品,如果按传统权限模型,给每一个用户赋予权限则会非常麻烦,并且做不到批量修改用户权限。这时候,可以抽象出几个角色,譬如销售经理、财务经理、市场经理等,然后把权限分配给这些角色,再把角色赋予用户。这样无论是分配权限还是以后的修改权限,只需要修改用户和角色的关系,或角色和权限的关系即可,更加灵活方便。此外,如果一个用户有多个角色,譬如王先生既负责销售部也负责市场部,那么可以给王先生赋予两个角色,即销售经理+市场经理,这样他就拥有这两个角色的所有权限 - 角色分层模型RBAC1
解析:
RBAC1建立在RBAC0基础之上,在角色中引入了继承的概念。简单理解就是,给角色可以分成几个等级,每个等级权限不同,从而实现更细粒度的权限管理。
image.png
举例:
基于之前RBAC0的例子,我们又发现一个公司的销售经理可能是分几个等级的,譬如除了销售经理,还有销售副经理,而销售副经理只有销售经理的部分权限。这时候,我们就可以采用RBAC1的分级模型,把销售经理这个角色分成多个等级,给销售副经理赋予较低的等级即可。 - 角色限制模型RBAC2
解析:
RBAC2同样建立在RBAC0基础之上,仅是对用户、角色和权限三者之间增加了一些限制。这些限制可以分成两类,即静态职责分离SSD(Static Separation of Duty)和动态职责分离DSD(Dynamic Separation of Duty)。具体限制如下图:
image.png
举例:
还是基于之前RBAC0的例子,我们又发现有些角色之间是需要互斥的,譬如给一个用户分配了销售经理的角色,就不能给他再赋予财务经理的角色了,否则他即可以录入合同又能自己审核合同;再譬如,有些公司对角色的升级十分看重,一个销售员要想升级到销售经理,必须先升级到销售主管,这时候就要采用先决条件限制了。 - 基于RBAC的延展——用户组
解析:
基于RBAC模型,还可以适当延展,使其更适合我们的产品。譬如增加用户组概念,直接给用户组分配角色,再把用户加入用户组。这样用户除了拥有自身的权限外,还拥有了所属用户组的所有权限。
举例:
譬如,我们可以把一个部门看成一个用户组,如销售部,财务部,再给这个部门直接赋予角色,使部门拥有部门权限,这样这个部门的所有用户都有了部门权限。用户组概念可以更方便的给群体用户授权,且不影响用户本来就拥有的角色权限。
2基于角色和资源的用户权限控制(用SpringMVC实现)
1.准备工作
新建数据库sshwebe:

建立数据表(由hibernate 对应模型类的映射文件自动建立各表与关系表)
通常,我们会混合使用上述的几种方式。其中,基于角色和资源是目前较为常见的一种实现,一般会有以下几张表:
用户表/学生表(users/t_students):id、用户名、密码、状态(是否可用,下同)
角色表(sb_roles):id、角色名、角色状态
权限表(sb_permissions):id、权限名、权限状态
除了这三张表外,还要两张表来将三者关联起来:
用户角色关联表(sb_students_roles):用户id、角色id(复合主键)
角色权限关联表(sb_roles_permissions):角色id、权限id(复合主键)
然后,我们在相应的controller中通过 annotation,或者在XML中定义相应的permission即可。
注:如果你不想自己实现,可以直接用Spring Security、Shiro等第三方安全框架,只要参照其规范建立几张表,进行一下配置即可。如果要自己实现也不难,这种权限控制模型,有点久是简单,缺点就是,角色被写死在数据库里,修改角色要修改数据库和源码,麻烦,所以使用与角色固定的中小系统,下面我们就通
过过滤器来实现。
初始化表记录:让程序一发布有一个超级管理员
1.学生:stuname:admin,password:admin123,state:1,regDate:2021-10-19
2.角色:roleName:超级管理员,state:1
3.学生-角色表:stu_id:1,roleId:1
建立各模型类
Student,Role,Permission类,Student类前面已有,只是改下:Student中把height字段删除,重新生成get,set和toString方法。
---再生成各自的dao,service层的接口与接口实现类,接口继承BaseDao<T>,BaseService<T>接口。实现类继承(extends)BaseDaoImpl,BaseServiceImpl,执行(implements)各自接口。
Role类:增加关联多对多单向权限类Permission,映射表中Role.hbm.xml,加如下内容。
------------------------------------------------------------------Role.java-----------------------------------------------------------
public class Role {
private int id;
private String roleName;
private Integer state; //0:表示禁 用,1:表示正常
private Set<Permission> permissions;
get,set,toString()等方法;//右击文件内容处,source---generate....
}
-----------------------------------Role.hbm.xml--------------------------------------------------------------------------------------
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="cn.ybzy.sshweb.model.Role" table="sb_roles">
<id name="id" type="int">
<column name="id" />
<generator class="native" />
</id>
<property name="roleName" type="java.lang.String">
<column name="rolename" />
</property>
<property name="state" type="java.lang.Integer">
<column name="state" />
</property>
<!--permissions是Role类中的一个成员,一个角色有多个权限,这是只是搞单向多对多关系
因为与easyui交互时,json数据格式会报懒加载异常,所有我们关闭懒加载
sb_roles_permissions是关联关系表,有字段:role_id,permission_id两个字段
-->
<set name="permissions" table="sb_roles_permissions" lazy="false">
<key>
<column name="role_id"></column>
</key>
<many-to-many class="cn.ybzy.sshweb.model.Permission" column="permission_id"></many-to-many>
</set>
Permission类:
public class Permission {
private int id;
private String resources; //这相权限能访问的资源
private Integer state; //状态0:表示禁用,1:表示正常
set,get,toString()方法。。。。
}
步1:”建立login.jsp文件,并实现http://localhost/项目名/login进行登陆页
1.从别处搞来的样式文件,login.css,login.jsp标签
如图所示它们的存放位置。

2.控制类中,设置返回login.jsp页。


步2:建立登录后的主页main.jsp 主要用于-用户管理--角色管理---权限管理
main.js主要作用是点击左侧的菜单,会在中心区面板上(easyui-tabs选项卡面板)添加选项是同菜单一样的选项。
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>熊少文管理后台</title>
<link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/resources/js/easyui/themes/default/easyui.css"></link>
<link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/resources/css/icon.css"></link>
<link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/resources/css/main.css"></link>
<script type="text/javascript" src="${pageContext.request.contextPath}/resources/js/jquery/jquery.min.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath}/resources/js/easyui/jquery.easyui.min.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath}/resources/js/easyui/locale/easyui-lang-zh_CN.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath}/resources/js/main.js"></script>
</head>
<body class="easyui-layout"> <!-- 把body样式布化局面,boder:false去掉边框 split:true即让标签与没显示的分隔开来 minHeight:60,maxHeight:60设置div上下不可拖动-->
<div class="sshweb_header" data-options="region:'north',border:false,split:true,minHeight:60,maxHeight:60">
<div class="sshweb_header_left">
<h1>Easyui+spring+springmvc+hibernate+根限控制模型</h1>
</div>
<div class="sshweb_header_right">
<p><strong>熊少文</strong>,欢迎你!</p>
<p><a href="#">网站首页</a>|<a href="#">帮助中心</a>|<a href="#">安全退出</a>
</div>
</div>
<div class="sshweb_sidebar" data-options="region:'west',border:true,split:true,title:'导航菜单',minWidth:160,maxWidth:160">
<div class="easyui-accordion" data-options="border:false,fit:true"> <!-- 折叠面板,没有连框,自适父容哭器大小 -->
<div title="系统设置" data-options="iconCls:'icon-wrench'" style="padding:5px;">
<ul class="easyui-tree sshweb_side_tree"> <!-- class名可有多个,用空格隔开 -->
<li iconCls="icon-users"><a href="#" data-icon="icon-users" data-link="${pageContext.request.contextPath}/studentManager" iframe="0">学生管理</a></li> <!--这里我们写第功能少,树节点写死在标签中,如果项目大,又要动态添加删除就要用到easy的异步访问数据库来作了 -->
<li iconCls="icon-user-group"><a href="#" data-icon="icon-user-group" data-link="${pageContext.request.contextPath}/roleManager" iframe="1">角色管理</a></li>
<li iconCls="icon-book"><a href="#" data-icon="icon-book" data-link="${pageContext.request.contextPath}/permissionManager" iframe="1">权限管理</a></li>
<li><a>数据字典</a></li>
</ul>
</div>
<div title="菜单管理" data-options="iconCls:'icon-wrench'" style="padding:5px;"></div>
<div title="订单管理" data-options="iconCls:'icon-wrench'" style="padding:5px;"></div>
</div>
</div>
<div class="sshweb_footer" data-options="region:'south',border:true,split:true">
©2021 sshweb All Right Reserved
</div>
<div class="sshweb_main" data-options="region:'center'">
<div id="sshweb_tabs" class="easyui-tabs" data-options="border:false,fit:true">
<div title="首页" data-options="href:'${pageContext.request.contextPath}/index',closable:false,iconCls:'icon-tip',cls:'pd3'"></div> <!--closable:false不可关闭 ,cls:'pd3' 相当 于 .pd3{样式列表,要在样式文件中写} -->
</div>
</div>
</body>
</html>
------------------------------------------------------------------main.js-----------------------------------------
$(function(){
$(".sshweb_side_tree li a").bind("click",function(){
var title = $(this).text();
var iconCls = $(this).attr("data-icon");
var url = $(this).attr("data-link");
var iframe = $(this).attr("iframe")==1?true:false;
addTab(title,url,iconCls,iframe);
});
});
function addTab(title,url,iconCls,iframe){
var tabPanel = $("#sshweb_tabs");
if(!tabPanel.tabs("exists",title)){
var content = "<iframe scrolling='auto' frameborder='0' src='"+url+"' style='width:100%;height:100%;'></iframe>";
if(iframe){
tabPanel.tabs("add",{
title:title,
content:content, //<iframe>放到tabs,灵活,里边的页面做任何操作,不会受,影响外面的页面,缺点:里边的用js,css,重复加载js文件,css
iconCls:iconCls,
fit:true,
cls:"pd3",
closable:true
});
}else{
tabPanel.tabs("add",{
title:title,
href:url, //本质是url指向的页面<body></body>,好处,不用重复的加载js文件,里边的这张页面,js,css,必须写在<body></body>,注意没url地址的datagrid的id不能重名
iconCls:iconCls,
fit:true,
cls:"pd3",
closable:true
});
}
}else{
tabPanel.tabs("select",title)
}
}
其中main.jsp中左侧栏,分对对应,用户管理,角色管理,权限管理 折叠面板--〉树节点
1<iframe>放到tabs,灵活,里边的页面做任何操作,不会受,影响外面的页面,缺点:里边的用js,css,重复加载js文件,css
2 data-link:url.url本质是url指向的页面<body></body>,好处,不用重复的加载js文件,里边的这张页面,js,css,必须写在<body></body>,注意没url地址的datagrid的id不能重名
<div class="sshweb_sidebar" data-options="region:'west',border:true,split:true,title:'导航菜单',minWidth:160,maxWidth:160">
<div class="easyui-accordion" data-options="border:false,fit:true"> <!-- 折叠面板,没有连框,自适父容哭器大小 -->
<div title="系统设置" data-options="iconCls:'icon-wrench'" style="padding:5px;">
<ul class="easyui-tree sshweb_side_tree"> <!-- class名可有多个,用空格隔开 -->
<li iconCls="icon-users"><a href="#" data-icon="icon-users" data-link="${pageContext.request.contextPath}/studentManager" iframe="0">学生管理</a></li> <!--这里我们写第功能少,树节点写死在标签中,如果项目大,又要动态添加删除就要用到easy的异步访问数据库来作了 -->
<li iconCls="icon-user-group"><a href="#" data-icon="icon-user-group" data-link="${pageContext.request.contextPath}/roleManager" iframe="1">角色管理</a></li>
<li iconCls="icon-book"><a href="#" data-icon="icon-book" data-link="${pageContext.request.contextPath}/permissionManager" iframe="1">权限管理</a></li>
<li><a>数据字典</a></li>
</ul>
</div>
<div title="菜单管理" data-options="iconCls:'icon-wrench'" style="padding:5px;"></div>
<div title="订单管理" data-options="iconCls:'icon-wrench'" style="padding:5px;"></div>
</div>
</div>
</div>

步3:建立三张视图 student_manager.jsp,role_manager.jsp,permission_manager.jsp
它们分别用于,操作‘用户管理’,‘角色管理’,‘权限管理’
它们(jsp文件)不是通过浏览器调用(如http://localhost/sshwebtest/student_manager.jsp)之打开的,而是通过 StuController.java,RoleController.java,PermissionController.java中的方法返回视图给main.jsp对应的选项卡面板中的。这里举两个说明返回视图的两种方法,
1.<iframe>放到tabs,灵活,里边的页面做任何操作,不会受,影响外面的页面,缺点:里边的用js,css,重复加载js文件,css
----------------------------------------------------------------RoleController.java---------------------------------------------
//用于主页main.jsp显示视图页 role_manager.jsp
@RequestMapping(value="/roleManager",method=RequestMethod.GET)
public String roleManager() {
return "role_manager";
}
------------------------------------------------------------role_manager.jsp---------------------------------------------------
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title></title>
<link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/resources/js/easyui/themes/default/easyui.css"></link>
<link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/resources/js/easyui/themes/icon.css"></link>
<link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/resources/css/index.css"></link>
<script type="text/javascript" src="${pageContext.request.contextPath}/resources/js/jquery/jquery.min.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath}/resources/js/easyui/jquery.easyui.min.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath}/resources/js/easyui/locale/easyui-lang-zh_CN.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath}/resources/js/role_manager.js"></script>
</head>
</head>
<body>
<!--由主页main.jsp <a href="#" data-icon="icon-user-group" data-link="${pageContext.request.contextPath}/roleManager" iframe="1">角色管理</a>,再后台RoleController--roleManager返回到该视图页面 -->
<div class="easyui-layout" data-options="fit:true">
<table id="rolesDatagrid"></table>
<div id="roleToolbar">
<div class="rolesToolbar_button" style="height: 40px;">
<a href="#" class="easyui-linkbutton" iconCls="icon-add" onclick="role_openAdd()" plain="true">添加</a>
<a href="#" class="easyui-linkbutton" iconCls="icon-edit" onclick="role_openEdit()" plain="true">修改</a>
<a href="#" class="easyui-linkbutton" iconCls="icon-remove" onclick="role_remove()" plain="true">删除</a>
<a href="#" class="easyui-linkbutton" iconCls="icon-cancel" onclick="role_cancel()" plain="true">取消</a>
<a href="#" class="easyui-linkbutton" iconCls="icon-reload" onclick="role_reload()" plain="true">刷新</a>
</div>
<!-- 角色管理不要模糊查询 -->
</div>
</div>
<div id="role_dialog" class="easyui-dialog" data-options="closed:true,iconCls:'icon-save'" style="width:400px;padding:4px;">
<form id="role_dialog_form" method="post">
<table>
<tr>
<td width="100" align="right">角色名称:<input type="hidden" id="id" name="id" /> </td>
<td><input type="text" id="roleName" name="roleName" class="easyui-textbox"/></td>
</tr>
<tr>
<td width="100" align="right">角色状态:</td>
<td><input type="text" id="state" name="state" class="easyui-textbox"/></td>
</tr>
<tr>
<td width="100" align="right">角色权限:</td>
<td><input type="text" id="permissionsId" name="permissions" class="easyui-combotree"
data-options="url:'/sshwebtest/getAllPermissions',multiple:true,required:true,panelHeight:133"/></td>
</tr>
</table>
</form>
</div>
</body>
2.本质是$.ajax({})中url指向的页面<body></body>,好处,不用重复的加载外部js,css文件,会共享main.jsp的.但里边的这张页面,js,css,必须写在<body></body>,注意没url地址的datagrid的id不能重名
学生管理:student_manager.jsp
-------------------------------------------------------------StuController.java------------------------------------------------------
//main.jsp中学生管理选项卡功能后台代码
@RequestMapping(value="/studentManager", method=RequestMethod.GET) //网页是默认为GET请求,除非改变方式
public String studentManager(){
return "student_manager";
}
---------------------------------------------------------student_manager.jsp该js代码也在该页中--------------------------------
<body>
<div class="easyui-layout" data-options="fit:true">
<table id="studentsDatagrid"></table>
<div id="studentsToolbar">
<div class="studentsToolbar_button" style="height: 40px;">
<a href="#" class="easyui-linkbutton" iconCls="icon-add" onclick="student_openAdd()" plain="true">添加</a>
<a href="#" class="easyui-linkbutton" iconCls="icon-edit" onclick="student_openEdit()" plain="true">修改</a>
<a href="#" class="easyui-linkbutton" iconCls="icon-remove" onclick="student_remove()" plain="true">删除</a>
<a href="#" class="easyui-linkbutton" iconCls="icon-cancel" onclick="student_cancel()" plain="true">取消</a>
<a href="#" class="easyui-linkbutton" iconCls="icon-reload" onclick="student_reload()" plain="true">刷新</a>
</div>
<div style="height: 40px;">
<label>用户名:</label><input class="easyui-textbox" id="student_searchbox" style="width:150px,height:24px;">
<a href="#" class="easyui-linkbutton" iconCls="icon-search" id="student_searchbtn">开始检索</a>
</div>
</div>
</div>
<div id="student_dialog" class="easyui-dialog" data-options="closed:true,iconCls:'icon-save'" style="width:400px;padding:4px;">
<form id="student_dialog_form" method="post">
<table>
<tr>
<td width="100" align="right">用户名称:<input type="hidden" id="id" name="id" /> </td>
<td><input type="text" id="stuname" name="stuname" class="easyui-textbox"/></td>
</tr>
<tr>
<td width="100" align="right">密 码:</td>
<td><input type="text" id="password" name="password" class="easyui-passwordbox" prompt="Password" iconWidth="28"/></td>
</tr>
<tr>
<td width="100" align="right">用户状态:</td>
<td><input type="text" id="state" name="state" class="easyui-textbox"/></td>
</tr>
<tr>
<td width="100" align="right">注册日期:</td>
<td><input type="text" id="regDate" name="regDate" class="easyui-datebox"/></td>
</tr>
<tr>
<td width="100" align="right">用户角色:</td>
<td><input type="text" id="rolesId" name="roles" class="easyui-combotree"
data-options="url:'/sshwebtest/getAllRoles',multiple:true,required:true,panelHeight:133"/></td>
</tr>
</table>
</form>
</div>
<script type="text/javascript">
$(function(){
$("#student_searchbtn").bind("click",function(){
var queryParams = $("#studentsDatagrid").datagrid("options").queryParams;
queryParams.stuname = $("#student_searchbox").textbox("getValue");
$("#studentsDatagrid").datagrid("load");
});
$("#studentsDatagrid").datagrid({
url:"/sshwebtest/getAllPagerStudents",
pagination:true, //让网格分页,
pageSize:10, //每一页显示5条
toolbar:"#studentsToolbar",
columns:[[
{field:"ck",checkbox:true},
{field:"id",title:"学生学号",width:200},
{field:"stuname",title:"学生姓名",width:200},
// {field:"password",title:"学生密码",width:200},//密码不能显示出来
{field:"state",title:"学生状态",width:200,formatter:function(value,row,index){
if(value == 1){
return "正常";
}else{
return "禁用";
}
}},
{field:"regDate",title:"注册日期",width:200},
{field:"roles",title:"学生角色",formatter:function(value,row,index){
var rolesStr = "";
if(value!=null && value.length>0){
for(var i=0; i<value.length;i++){
rolesStr+="【"+value[i].roleName+"】"
}
}
return rolesStr;
}},
]],
})
});
function student_openAdd(){
$("#student_dialog_form").form("clear"); //清理表单
$("#student_dialog").dialog({
title:"添加用户",
closed:false, //显示弹出对话框
modal:true,
onOpen:function(){ //加载成功对话框时,它里的用户不能修改,且处于不可编辑状态
//$("#stuname").attr("disabled","disabled"); //jq代码不起作用,从浏览器中查看器中可以看到,它打开时隐藏了
$("#stuname").textbox({disabled:false}) //这时添加用户要处于可编辑状态
},
buttons:[
{
text:"确定",iconCls:"icon-ok",
handler:student_add
},
{
text:"取消",iconCls:"icon-cancel",
handler:function(){
$("#student_dialog").dialog("close"); //关闭弹出对话框
}
}
]
})
}
//修改学生信息,先选中网格中的左侧复选框,再修改,注:选择多行时提示,不能修改,一次只能修改一行。
//修改:不同于添加,添加中id自增长,可通过ajax不用form,而修改要通过id,但id是隐藏表单,所有用到form.
function student_openEdit(){
$("#student_dialog_form").form("clear");
var rows = $("#studentsDatagrid").datagrid("getSelections"); //选中的行数据,没有返回null
if(rows.length>1){
$.messager.alert("信息提示:","一次只能修改一条数据!","info");
}else if(rows.length == 0){
$.messager.alert("信息提示","请您勾选要修改的数据项","info");
}else{
var student = rows[0];
student.password=""; //修改用户不能加载密码进来
$("#student_dialog_form").form("load",student); //esayui中,表单通过load自动按name属性值加载数据到表单域中,包括复杂的combotree(role)也是一样,因为它们始终在页面,不用转json格式
$("#student_dialog").dialog({
title:"修改用户",
closed:false, //显示弹出对话框
modal:true,
onOpen:function(){ //加载成功对话框时,它里的用户不能修改,且处于不可编辑状态
//$("#stuname").attr("disabled","disabled"); //jq代码不起作用,从浏览器中查看器中可以看到,它打开时隐藏了
$("#stuname").textbox({disabled:true})
},
buttons:[
{
text:"确定",iconCls:"icon-ok",
handler:student_edit
},
{
text:"取消",iconCls:"icon-cancel",
handler:function(){
$("#student_dialog").dialog("close"); //关闭弹出对话框
}
}
]
})
}
}
//修改用户信息
function student_edit(){
doAjax("/sshwebtest/updateStudentmanager")
}
//添加用户信息
function student_add(){
doAjax("/sshwebtest/addStudent")
}
//提取公共代码放在一个方法中,上面添加,修改,确定功ajax能绝大多数代码是一样的,只有url:不一样
function doAjax(param){ //确定按钮,要干的活(添加学生信息到数据库中,修改信息(按id)
//1.第一种思路,在js中封装数据,其实就是处理对象类型数据,转为json格式发送到后台处理到数据库中。
//先拿到rolesId:[1,2,3...]--->1,2,3--->[{id:1},{id:2}..]
var rolesId =$("#rolesId").val(); //该对象是combotree对象发送的请求从浏览器的网络-请求中可以看到是数组[{},{}...]对象
var rolesArr = rolesId.split(",");
var roleObjStr = "[";
for(var i=0;i<rolesArr.length;i++){
roleObjStr+="{id:"+rolesArr[i]+"},";
}//最终结果:[{id:1},{id:2},....,{id:n},
roleObjStr = roleObjStr.substring(0,roleObjStr.length-1)+"]" //去掉最后一个逗号,再加]--->[{id:1},{id:2},....,{id:n}]
var roleObj = eval("("+roleObjStr+")"); //([{id:1},{id:2},....,{id:n}])为一个json对象
$.ajax({
url:param,
method:"post",
contentType:"application/json;charset=utf-8", //指定传送请求的数据为json格式,如果是8种基本数据类型,就不用这种格式
data:JSON.stringify({ //封装请求
id:$("#id").val(), //这个修改时一定要的,添加不要的,但写上也没事
stuname:$("#stuname").val(),
password:$("#password").val(),
state:$("#state").val(),
regDate:$("#regDate").val(),
roles:roleObj
}),
success:function(data){ //"ok","error"
if(data == "ok"){
$("#student_dialog").dialog("close");
$("#studentsDatagrid").datagrid("reload");
}else{
$.messager.alert("信息提示","提交失败!","info");
}
}
});
}
function student_remove() {
var rows = $("#studentsDatagrid").datagrid("getSelections");
if(rows.length < 1){
$.messager.alert("信息提示","请勾选您要删除的数据!","info");
}else{
$.messager.confirm("信息提示","确定要删除选中的记录吗?",function(result){
if(result){
var ids = [];
$(rows).each(function(){
ids.push(this.id);
});
alert(ids);
$.ajax({
url:"/sshwebtest/deleteStudent",
method:"post",
data:{
ids:ids //删除用户的请求只有ids,按id来删除的
},
success:function(data){ //"ok","error"
if(data == "ok"){
$("#studentsDatagrid").datagrid("reload");
}else{
$.messager.alert("信息提示","删除失败!","info");
}
}
});
}
});
}
}
function student_cancel() {
$("#studentsDatagrid").datagrid("rejectChanges");
}
function student_reload() {
$("#studentsDatagrid").datagrid("reload");
}
</script>
</body>
注:
1.permission_manager.jsp这里不再举例,代码差不多,只是改一些字段而已。各后台功能代码见各自的控制类。
2.用户一旦注册用户名不能修改,修改用户时,因为弹框没有加载密码,所以没有修改的情况下,按确认键后,保存密码是空白的到数据库中,这也要处理好,不能这样。
3.复杂数据即不是基本数据类型的数据,jq的ajax与后台传递时,一定要转成json格式,或后台处理,或前前端处理,这些处理方法我已有相关笔记。
4.保存用户密码一定要加密处理,加密代码:如下代码:
加密工具:
public class SecurityUtil {
public static void main(String[] args) {
System.out.println(md5("admin", "admin123"));
}
//项目中使用的加密的算法,比较流行的算法:MDX(MD2,MD5),SHA-X(SHA-1,SHA-256,384,512) 可以说不能反过来推算到明文,
public static String md5(String str) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");//这个参数就是加密的算法
md.update(str.getBytes()); // 明文字符串更新到md对象里
byte[] rs = md.digest(); //做加密计算,计算结果就是密文
//byte[]转16进制的字符串,BigInteger大整数
return new BigInteger(1, rs).toString(16); //byte数据不可返回,要转成字符串或int啥的,1表十六制,不可为其它数,16表十六进制
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
//加密深一点
public static String md5(String str1,String str2) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");//这个参数就是加密的算法
md.update(str1.getBytes()); // 明文字符串更新到md对象里
md.update(str2.getBytes());
//md.update("abcxiong".getBytes()); //自定义自已加的盐值,也是进一步加密
byte[] rs = md.digest(); //做加密计算,计算结果就是密文
//byte[]转16进制的字符串,BigInteger大整数
return new BigInteger(1, rs).toString(16);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null; //万一MD5不存在,返回null,不太可能执行到这一步
}
//SHA-1加密算法
public static String sha1(String str) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-512");
md.update(str.getBytes());
byte[] rs = md.digest();
StringBuffer sb = new StringBuffer(); //因为是一个一个字符的拼且,StringBuffer比String高效
for(byte b:rs) {
//可以将byte类型转换16进制格式是数字符串
sb.append(String.format("%02x", b)); //%02x,一个byte类型整数----> 2位十六进制字符来表示
}
return sb.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
}
StuController.java应用加密工具举例
@ResponseBody
@RequestMapping(value="/addStudent",method=RequestMethod.POST)
public String addStudent(@RequestBody Student student) throws ParseException{ //直接接收对象,
String password = student.getPassword();
String stuname = student.getStuname();
password = SecurityUtil.md5(stuname,password); //给密码加密保护
student.setPassword(password);
try {
stuService.add(student);
}catch(Exception e) {
return "error";
}
return "ok";
}
后台实现权限控制。
初始化数据库,最初始只建一个学生(t_students)账号,admin,密码admin123:加密的密码,上面的java加密代码中加密一个复制到表中。sb_roles表中,只加一个超级管理员一个角色,state=1
实现权限控制的大体思路
- 用EasyUI把一个典型后台页面布局搭建起来:
前面已完成
2. 约定权限控制的注解
需要权限控制的类:@AuthClass, 需要权限控制的方法:@AuthMethod,那么就要建立对应的注解类
- 新建两个注解类,一个应用到类上,一个应用到方法上。
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
//该注解将用在controller属性类的的方法上
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthMethod {
public String value() default "";
}
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME) //指定定义的注解类@AuthClass的生命周期,即在运行时有效
public @interface AuthClass {
public String value() default "";
}
- 把@AuthClass,@AuthMethod注解加到控制类(StuController.java,PermissionController.java,RoleController.java)的类上和所有方法上。这里举一个RoleController.java例子。
@AuthClass //权限控制注解类的注解
@Controller
public class RoleController {
@Autowired
private RoleService roleService; //自动获取IOC中的角色对象
//用于主页main.jsp显示视图页 role_manager.jsp
@AuthMethod
@RequestMapping(value="/roleManager",method=RequestMethod.GET)
public String roleManager() {
return "role_manager";
}
@AuthMethod
@ResponseBody
@RequestMapping(value="/getAllRoles",method=RequestMethod.POST)
public List<Map<String,String>> getAllRoles(){
List<Role> roles = roleService.getAllRoles();
List<Map<String,String>> roleList = new ArrayList<>();
for(Role role:roles) {
Map<String,String> roleMap = new HashMap<>();
roleMap.put("id", role.getId()+"");
roleMap.put("text", role.getRoleName()); //转成text:roleName
roleList.add(roleMap);
}
return roleList; //roleList通过@ResponseBody响应json格式给组合树,[{id:xx,text:xxx},....]
}
//支持分页显示的查询方法,比分显示学生信息--少模糊查询代码,不用获取按xxx查询的参数
@AuthMethod
@ResponseBody
@RequestMapping(value="/getAllPagerRoles",method=RequestMethod.POST)
public Pager<Role> getAllPagerRoles(Integer page,Integer rows) //page,rows值由easyui 网格自动发送的请求而过来的
{
if(page != null && page>0) SystemContext.setPageSize(rows); //每一页两条记录
if(rows !=null && rows>0) SystemContext.setPageOffset((page-1)*rows); //从第一条记录开始分
Pager<Role> roles = roleService.getAllPagerRoles();
SystemContext.removePageOffset();
SystemContext.removePageSize();
return roles;
}
/**
* 角色管理-添加记录
*/
@AuthMethod
@ResponseBody
@RequestMapping(value="/addRole",method=RequestMethod.POST)
public String addRole(@RequestBody Role role){
try {
roleService.add(role);
}catch(Exception e) {
return "error";
}
return "ok";
}
/**
* 角色管理-修改记录
*/
@AuthMethod
@ResponseBody
@RequestMapping(value="/updataRole",method=RequestMethod.POST)
public String updateRole(@RequestBody Role stu) {
try {
roleService.update(stu);
}catch(Exception e) {
return "error";
}
return "ok";
}
/**
* 角色管理-删除记录
* @param ids
* @return
*/
@AuthMethod
@ResponseBody //返回json格式到网页中
@RequestMapping(value="/deleteRole", method=RequestMethod.POST)
public String deleteStudent(@RequestParam(value="ids[]") int[] ids) {
try {
for(int id:ids) {
roleService.delete(id);
}
}catch(Exception e) {
return "error";
}
return "ok";
}
}
3系统启动的时候将系统的权限标记保存到permissions数据表里,初始化权限。
InitWebServlet.java
该方法继承HttpServlet,其中重写了init方法,就是让web容器(tomcat服务)一起动就,把权限标记(如 '/addStudent')加到数据库表中sb_permissions,再main.jsp界面的权限管理中可以读到所有权限.
注:获取权限标记步子:
1加了AuthClass,AuthMethod注解。
2.获取springmvcIOC容器 applicationContext.
3.反射方式获取各自控制类的方法
4.依据方法名获取@RequestMapping(value={"/addStudnet","",""})的value[0]的值,value是一个数组。
为什么用list,set集合,而不用数组呢,因为数组要定义个数,这很不好。
Context整个web全局空间里保存了String allPermRes-当前学生所拥有对‘学生,角色,权限’三个模块操作的权限标记。为拦截器(AuthIncepertor.java)对比权限。
public class InitWebServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
// 两个初始化: 一,初始化:spring的ioc容器的引用到InitWebServlet类的一个静态方法上来
private static ApplicationContext applicationContext;
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
// 二,初始化:初始化权限。
@Override
public void init() throws ServletException {
// 一 初始化:spring的ico容器
ServletContext context = getServletContext();
applicationContext = WebApplicationContextUtils.getWebApplicationContext(context);
//二,初始化:权限
//packageName实施权限控制的包全名(控制controller层)
try {
String packageName = "cn.ybzy.sshweb.controller";
String packageNamePath = packageName.replace(".", "/"); //转为路径格式
//获取到服务器上磁盘上的绝对路径,不仅仅是根目录
String packageNameRealPath = this.getClass().getClassLoader().getResource(packageNamePath).getPath();
//System.out.println("权限控制InitWebServlet-packageNameRealPath=====>"+packageNameRealPath); //C:/Users/Administrator/Desktop/apache-tomcat-8.5.55/webapps/sshwebtest/WEB-INF/classes/cn/ybzy/sshweb/controller/
File file =new File(packageNameRealPath);
//遍历这个文件夹里的文件,拿到编译后的所有后缀为.class文件组字符串名。
String [] classFileNames = file.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
if(name.endsWith(".class")) {
return true;
}
return false;
}
});
List<String> resources =new ArrayList<>();
//String[] resources1 = new String[10]; //为什么用List而不用数组呢,因为数组要定多少个,这很不好,因为我们根本不知道有多少个
for(String classFileName:classFileNames) {
//删除所有的.class后缀
classFileName = classFileName.substring(0,classFileName.indexOf(".class"));
//拿到纯粹的包全名,如 cn.ybzy.sshweb.controller.StuController没有后缀
String classAllpackageName = packageName+"."+classFileName;
//可以通过他们拉包全名获取到对应的类对象
Class clazz = Class.forName(classAllpackageName);
//拿到这些controller的对象,获取到在他们身上的注解,如果没有AuthClass注解,不作为,继续下面的代码执行
if(!clazz.isAnnotationPresent(AuthClass.class))
continue; //结束本次外循环,接着下一次外循环
//剩下的类,都是有@AuthClass这个注解类,这些类都要进行权限控制的
//拿到这个对象的所有方法
Method[] methods = clazz.getDeclaredMethods();
for(Method method:methods) {
if(!method.isAnnotationPresent(AuthMethod.class))
continue; //if该方法上没有注解@AuthMethod结束本次内循环,接关下一次内循环
//方法有@AuthMethod的方汉,拿到要保存到sb_permissions表里的resources字段的值,再看页面映射到后台的返回值,根据resources来判定用户有没有权操作相关功能
//1:cn.ybzy.sshweb.controller.addUser
// String resource = classAllpackageName+"."+method.getName();
//2:/addUser
RequestMapping reqMapping = method.getAnnotation(RequestMapping.class);
String resource = reqMapping.value()[0];
resources.add(resource);
}
}
//到这里,List<String> resources:包含了controller包里,所有被@AuthClass和@AuthMethod共同作用的方法上面的@RequestMapping的value值,该value是一个数组对象,如@RequestMapping(value= {"","/","/index"}),都在里面
//Servlet类中不同样@Controller注解的类,可以自动封装IOC中的类(@Autowrite),要调用应用应用上下文对象.getBean(),
PermissionService permissionService = (PermissionService) applicationContext.getBean("permissionService");
permissionService.initPermissions(resources); //把所有权限标记注入到数据库中
context.setAttribute("allPermRes", resources); //放进整个项目全局域中
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
------------------------------------------------------------------web.xml------------------------------------------------------------------
<!-- 系统初始化的servlet,它应该随着系统的启动而自动启动 -->
<servlet>
<servlet-name>InitWebServlet</servlet-name>
<servlet-class>cn.ybzy.sshweb.web.InitWebServlet</servlet-class>
<load-on-startup>1</load-on-startup> <!-- 1表示先于2启动 ,自调用里面的init方法-->
</servlet>

4. 当用户登录的时候
获取该用户所对应的角色,再根据角色获取其拥有的permission集合
- login.jsp里,登陆时会发生两个请求,一个是GET(浏览器一打开jsp),一个是POST(表单)在这个控制类要分开写两个login映射到main.jsp方法。
- 建一个HttpSession对象,用于保存' loginStudent'登陆成功的学生,超级管理员的标志'isadmin'这个用于区分超管与普管等其它角色的标志,如果是超管,一路放行,其它要专门加权限。与'studentAllpermResources '当前用户所有的权限标记,用于拦截器不拦截该权限所对应的映射方法。
LoginController.java
@Controller
public class LoginController {
@Autowired
private StudentService studentService;
/**
* 这里首页其实是登录页
* @return
*/
@RequestMapping(value= "/login",method=RequestMethod.GET)
public String login() {
return "login";
}
@RequestMapping(value= "/login",method=RequestMethod.POST)
public String login(String stuname, String password, HttpSession session) {
//判断用户填写的stuname和password对不对
Student loginStudent = studentService.login(stuname,password); //返回Student对象,抛出异常信息
//System.out.println("===========loginStudent:" + loginStudent);
session.setAttribute("loginStudent", loginStudent);
//判断一下登录成功的用户是不是超级管理员
Set<Role> roles = loginStudent.getRoles(); //拿到登录成功的用户所有关联的所有角色信息
boolean isadmin = false; //是:true,不是:false
Set<String> studentAllpermResources = new HashSet<>();
Set<Permission> permissions;
for(Role role:roles) {
if("超级管理员".equals(role.getRoleName())) {
isadmin = true;// 标记改true
break; // 超级管理员,循环没有了意义
}
//不是超级管理员的情况下,我们要把登录成功的用户,关联的所有权限标记,取出来
permissions = role.getPermissions();
for(Permission permission : permissions) {
studentAllpermResources.add(permission.getResource());
}
}
//循环完毕后,userAllpermRes:包括了登录成功的用户,所拥有的所有权限的标记
session.setAttribute("isadmin", isadmin);
if(!isadmin) {
session.setAttribute("studentAllpermResources", studentAllpermResources);
}
//后面的东西都围绕loginUser来进行的
return "redirect:/main";
}
@RequestMapping(value="/main")
public String main() {
return "main";
}
@RequestMapping(value="/main/index",method=RequestMethod.GET)
public String mainIndex() {
return "/main_index";
}
//安全退出映射方法
@RequestMapping(value="/logout",method=RequestMethod.GET)
public String logout(HttpSession session) {
session.removeAttribute("isadmin");
session.removeAttribute("studentAllpermResources");
session.removeAttribute("loginStudent");
session.invalidate();
return "/login";
}
}
5. 当用户访问受限资源的时候,
将访问的资源和用户拥有的权限资源进行对比,判断是否有访问权限。用拦截器实现拦截,有权限标记不拦截。
AuthIncepertor.java
public class AuthIncepertor extends HandlerInterceptorAdapter {
@SuppressWarnings("unchecked")
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
String resource = "";
if(handler instanceof HandlerMethod) { //前面的页面url地址,乱写的,false
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
resource = requestMapping.value()[0]; // 当前访问的页面的对应的权限标记
}else {
throw new RuntimeException("您访问的页面不存在!");
}
//拿到系统中所有要求进行权限控制的方法,对应的权限控制标记
List<String> resources = (List<String>) request.getServletContext().getAttribute("allPermRes");
//拿到目前登录的用户,他所拥有的所有的权限对应的权限控制标记
Set<String> userAllpermRes = (Set<String>) session.getAttribute("userAllpermRes");
//拿到目前登录成功的用户的对象
User loginUser = (User) session.getAttribute("loginUser");
if(loginUser == null) {
response.sendRedirect(request.getContextPath() + "/login");
}else {
boolean isadmin = (boolean) session.getAttribute("isadmin");
if(!isadmin && resources.contains(resource)) {
//进行权限控制的:对比两个东西,userAllpermRes包不包含resource
if(!userAllpermRes.contains(resource)) {
throw new RuntimeException("您没有权限访问该功能!");
}
}
}
return super.preHandle(request, response, handler);
}
}
-------------------------------------------------------------springmvc.xml配拦截器---------------------------------------------------
<!-- 配置springmvc的进行权限控制的拦截器 -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/login"/> <!--不拦截登陆页-->
<mvc:exclude-mapping path="/resources/**"/> <!--不拦截静态资源如图片,CSS,JS等文件-->
<bean class="cn.ybzy.sshweb.web.AuthIncepertor"></bean>
</mvc:interceptor>
</mvc:interceptors>
总结权限控制过程:
1.当一个学生(已在添加用户中加入某一角色-对应某些权限)登入(http://localhost:8080/sshwebtest/login--->login是控制类LoginController.java的一个映射到login.jsp的方法)网站时(login.jsp)--<form action="${pageContext.request.contextPath}/login" method="post"><input type="submit" value="Login">
如成功登陆(由LoginController.java的login()方法来判断用户名,与密码是否正确)会回到main.jsp页面。
2.登录成功后,可以按相应的权限进行操作。(操作有:添加,删除,取消,修改---用户,角色,权限)



