1 SSM权限管理系统-项目环境搭建 在创建Maven项目时,添加archetypeCatalog=internal 配置pom.xml的mvean依赖,添加逆向生成工具. 创建需要的目录domain,mapper,service,web,resources 替换web.xml,修改为3.0的约束 <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0" metadata-complete="false"> <absolute-ordering/> <display-name>web</display-name> <welcome-file-list> <welcome-file>index.html</welcome-file> <welcome-file>index.htm</welcome-file> <welcome-file>index.jsp</welcome-file> </welcome-file-list> <!--配置前端控制器--> <servlet> <servlet-name>SpringMVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <!--加载的主配置文件--> <param-value>classpath:applicationContext.xml</param-value> </init-param> <!-- 项目启动就加载框架 --> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>SpringMVC</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!-- 编码过滤器 --> <filter> <filter-name>CharacterEncoding</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncoding</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app> 2. SSM权限管理-EasyUI主页框架 EasyUI主页 1.在目录当中引入EasyUI相关JS与css 2.在首页当中引入所需要的js与css <link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/static/plugins/easyui/uimaker/easyui.css"> <link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/static/plugins/easyui/uimaker/icon.css"> <script type="text/javascript" src="${pageContext.request.contextPath}/static/plugins/easyui/jquery.min.js"></script> <script type="text/javascript" src="${pageContext.request.contextPath}/static/plugins/easyui/jquery.easyui.min.js"></script> <script type="text/javascript" src="${pageContext.request.contextPath}/static/plugins/easyui/easyui-lang-zh_CN.js"></script> <script type="text/javascript" src="${pageContext.request.contextPath}/static/js/index.js"></script> 3.编写body所首页框架格式 <body class="easyui-layout"> <%--顶部--%> <div data-options="region:\'north\'" style="height:100px; background: #ec4e00; padding: 20px 20px"> <img src="static/images/main_logo.png" alt=""> </div> <%--底部--%> <div data-options="region:\'south\'" style="height:50px; border-bottom: 3px solid #ec4e00"> <p align="center" style="font-size: 14px">撩课学院</p> </div> <%--左侧菜单--%> <div data-options="region:\'west\',split:true" style="width:300px;"> <div id="aa" class="easyui-accordion" data-options="fit:true"> <div title="菜单" data-options="iconCls:\'icon-save\',selected:true" style="overflow:auto;padding:10px;"> <!--tree--> <ul id="tree"></ul> </div> <div title="公告" data-options="iconCls:\'icon-reload\'" style="padding:10px;"> </div> </div> </div> <%--右侧主区域--%> <div data-options="region:\'center\'" style="background:#eee;"> <!--标签--> <div id="tabs" style="overflow: hidden" ></div> </div> </body> 4.创建首页index.js引入 $(function () { $("#tabs").tabs({ fit:true }) $(\'#tree\').tree({ url:\'/getTreeData\', lines:true, onSelect: function(node){ /*在添加之前, 做判断 判断这个标签是否存在 */ var exists = $("#tabs").tabs("exists",node.text); if(exists){ /*存在,就让它选中*/ $("#tabs").tabs("select",node.text); }else { if (node.url !=\'\'&& node.url !=null){ /*如果不存在 ,添加新标签*/ $("#tabs").tabs("add",{ title:node.text, /*href:node.attributes.url,*/ /*href 引入的是body当中*/ content:"<iframe src="+node.url+" frameborder=\'0\' width=\'100%\' height=\'100%\'></iframe>", closable:true }) } } }, onLoadSuccess: function (node, data) { console.log(data[0].children[0].id); if (data.length > 0) { //找到第一个元素 var n = $(\'#tree\').tree(\'find\', data[0].children[0].id); //调用选中事件 $(\'#tree\').tree(\'select\', n.target); } } }); }); 3. 菜单标签跳转 创建3个controller EmployeeController MenuController RoleController 控制拦截url为employee,role,menu 跳转到相应的页面,比如EmployeeController @Controller public class EmployeeController { @RequestMapping("/employee") public String employee(){ return "employee"; } } index.js上面的 onLoadSuccess: function (node, data) { console.log(data[0].children[0].id); if (data.length > 0) { //找到第一个元素 var n = $(\'#tree\').tree(\'find\', data[0].children[0].id); //调用选中事件 $(\'#tree\').tree(\'select\', n.target); } } }); 表示在页面初始时调用一个元素.让页面初始时右侧不为空. 注意需要清空缓存. 4. 主页数据表格添加 将共用的scrpit和link抽成一个包. common.jsp <link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/static/plugins/easyui/uimaker/easyui.css"> <link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/static/plugins/easyui/uimaker/icon.css"> <script type="text/javascript" src="${pageContext.request.contextPath}/static/plugins/easyui/jquery.min.js"></script> <script type="text/javascript" src="${pageContext.request.contextPath}/static/plugins/easyui/jquery.easyui.min.js"></script> <script type="text/javascript" src="${pageContext.request.contextPath}/static/plugins/easyui/easyui-lang-zh_CN.js"></script> <script type="text/javascript" src="${pageContext.request.contextPath}/static/js/base.js"></script> 在index.jsp中引用 <head> <title>权限管理系统</title> <%@include file="/static/common/common.jsp"%> <script type="text/javascript" src="${pageContext.request.contextPath}/static/js/index.js"></script> </head> index.js属于index.jsp独有的. 在employee.jsp中引用 <head> <title>Title</title> <%@include file="/static/common/common.jsp"%> </head> 给employee页面添加employee.js $(function(){ $("#dg").datagrid({ url:"/employeeList", columns:[[ {field:\'username\',title:\'姓名\',width:100,align:\'center\'}, {field:\'inputtime\',title:\'入职时间\',width:100,align:\'center\'}, {field:\'tel\',title:\'电话\',width:100,align:\'center\'}, {field:\'email\',title:\'邮箱\',width:100,align:\'center\'}, {field:\'department\',title:\'部门\',width:100,align:\'center\'}, {field:\'state\',title:\'状态\',width:100,align:\'center\'}, {field:\'admin\',title:\'管理员\',width:100,align:\'center\'}, ]], fit:true, /*是否需要填满*/ fitColumns:true, /*是否自动伸缩*/ rownumbers:true, /*是否需要显示行号*/ pagination:true /*是否需要分页*/ }) }); 5. 员工表建立 设计数据库的表. 建立新的数据库promission 修改逆向工程的generatorConfig.xml,修改配置中的数据库名和表名. 逆向生成Employee,EmployeeMapper,EmployeeMapper.xml 注意把EmployeeMapper.xml需要放在resources下面新建的com.itlike.mapper. 6. 员工列表查询 创建一个业务接口service,employeeService.实现类employeeServiceImpl. 将其标记为@Service public class employeeServiceImpl implements employeeService { @Autowired private EmployeeMapper employeeMapper; @Override public void getEmployee() { System.out.println("来到业务层"); /*调用mapper查询员工*/ employeeMapper.selectAll(); } } 修改controller层,注入service,添加@RequestMapping("/employeeList") EmployeeController @Controller public class EmployeeController { /*注入业务层*/ @Autowired private employeeService employeeService; @RequestMapping("/employee") public String employee(){ return "employee"; } @RequestMapping("/employeeList") public void employeeList(){ /*调用业务层查询员工*/ employeeService.getEmployee(); } } 注意修改db.properties下面的数据库名jdbc.url=jdbc:mysql://localhost:3306/promission?characterEncoding=utf-8 新建一个PageListRes的domain类. 由于easyUI的datagrid需要有total分页和rows数组类型的json PageListRes类. @Getter@Setter public class PageListRes { private Long total; private List<?> rows=new ArrayList<>(); } 修改employeeServiceImpl,使用pageHelper来进行分页 @Service public class employeeServiceImpl implements employeeService { @Autowired private EmployeeMapper employeeMapper; @Override public PageListRes getEmployee() { /*调用mapper查询员工*/ /*设置分页total*/ Page<Object> page = PageHelper.startPage(1, 5); List<Employee> employees = employeeMapper.selectAll(); /*封装成pageList*/ PageListRes pageListRes = new PageListRes(); pageListRes.setTotal(page.getTotal()); pageListRes.setRows(employees); return pageListRes; } } 修改接口employeeService,将返回值类型改为PageListRes 前端控制器修改EmployeeController @Controller public class EmployeeController { /*注入业务层*/ @Autowired private employeeService employeeService; @RequestMapping("/employeeList") @ResponseBody public PageListRes employeeList(){ /*调用业务层查询员工*/ PageListRes pageListRes = employeeService.getEmployee(); return pageListRes; } 使用@ResponseBody获取json数据 7. 日期格式化 由于上一节已经可以取到数据,并且显示在前端页面,但是日期格式不对,需要格式化日期数据 在employee.js做数据格式化 $(function(){ $("#dg").datagrid({ url:"/employeeList", columns:[[ {field:\'username\',title:\'姓名\',width:100,align:\'center\'}, {field:\'inputtime\',title:\'入职时间\',width:100,align:\'center\'}, {field:\'tel\',title:\'电话\',width:100,align:\'center\'}, {field:\'email\',title:\'邮箱\',width:100,align:\'center\'}, {field:\'department\',title:\'部门\',width:100,align:\'center\'}, {field:\'state\',title:\'状态\',width:100,align:\'center\',formatter:function (value,row,index) { if (row.state){ return "在职"; }else { return "<font style=\'color:red\'>离职</font>" } }}, {field:\'admin\',title:\'管理员\',width:100,align:\'center\',formatter:function (value,row,index) { if (row.admin){ return "是"; }else { return "否"; } }}, ]], fit:true, /*是否需要填满*/ fitColumns:true, /*是否自动伸缩*/ rownumbers:true, /*是否需要显示行号*/ pagination:true /*是否需要分页*/ }) }); 添加管理员和状态的判断,true为在职和是,false为离职和否. 在domain的实体类中添加注解 @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") private Date inputtime; 设置时间格式为GMT+8小时. 或者使用 @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") 8. 部门表创建 包含id和name,在employee表建立外键dep_id 参考表department参考字段id. 逆向生成domain和mapper,mapper.xml. 将xml放在resources下面. 9. 员工部门关联查询. 修改Employee的部门字段,需要修改为和employee.js的字段相同. private Department department; 修改数据库查询语句,使用左连接当两个表id相等时. <select id="selectAll" resultMap="BaseResultMap" > select e.id, e.username, e.inputtime, e.tel, e.email, e.state, e.admin , d.id as d_id, d.`name` as d_name from employee as e LEFT JOIN department as d ON e.dep_id=d.id; </select> 修改 <result column="dep_id" property="dep_id" jdbcType="bigint" />为下面的 注意columnPrefix="d_",因为上面使用了as修改了别名,所有要加上d_,不然无法映射 <association property="department" javaType="com.itlike.domain.Department" columnPrefix="d_"> <result property="id" column="id"/> <result property="name" column="name"/> </association> 实现了查询部门名称,接下来再在前端去使用数据. 注意需要删除上面查询语句中的dep_id 注意SQL语句结尾不能添加; 否则sql语句不会执行. 修改前端的js,如果有部门则显示名字,注意使用value. {field:\'department\',title:\'部门\',width:100,align:\'center\',formatter:function (value,row,index) { if(value.name){ return value.name; }else { return "无部门"; } }}, 在多表查询有字段名相同时,起一个别名,在封装时注意使用columnPrefix. 10. 添加对话框搭建 列表添加工具栏目 employee.jsp <body> <div id="tb"> <a href="#" class="easyui-linkbutton" data-options="iconCls:\'icon-add\',plain:true" id="add">添加</a> <a href="#" class="easyui-linkbutton" data-options="iconCls:\'icon-edit\',plain:true" id="edit">编辑</a> <a href="#" class="easyui-linkbutton" data-options="iconCls:\'icon-remove\',plain:true" id="delete">删除</a> <a href="#" class="easyui-linkbutton" data-options="iconCls:\'icon-reload\',plain:true" id="reload">刷新</a> </div> <table id="dg"></table> </body> 修改前端样式,添加对话框 <%--对话框--%> <div id="dialog"> <table align="center" style="border-spacing: 0px 10px"> <tr> <td>用户名:</td> <td><input type="text" class="easyui-validatebox" data-options="required:true"></td> </tr> <tr> <td>密码:</td> <td><input type="password" class="easyui-validatebox" data-options="required:true"></td> </tr> <tr> <td>手机:</td> <td><input type="text" class="easyui-validatebox" ></td> </tr> <tr> <td>邮箱:</td> <td><input type="text" class="easyui-validatebox" ></td> </tr> <tr> <td>入职日期:</td> <td><input type="text" class="easyui-datebox"></td> </tr> <tr> <td>部门:</td> <td><select id="department"></select></td> </tr> <tr> <td>是否管理员:</td> <td><select id="state"></select></td> </tr> </table> </div> 添加增删编辑刷新按钮. employee.js 监听添加按钮点击 /*对话框*/ $("#dialog").dialog({ width:350, height:350, closed:true //初始默认为隐藏 }); /*监听添加按钮点击*/ $("#add").click(function () { $("#dialog").dialog("open"); //监听点击时open }) 11. 下拉列表placeholder处理 修改jsp中的页面,将select改为input,添加placeholder属性 <tr> <td>部门:</td> <td><input id="department" placeholder="请选择部门"/></td> </tr> <tr> <td>是否管理员:</td> <td><input id="state" placeholder="是否为管理员"/></td> </tr> 在js中添加下拉列表,选中id为state和department的行 /*部门选择 下拉列表*/ $("#department").combobox({ width:150, panelHeight:\'auto\' }); /*是否为管理员 下拉列表*/ $("#state").combobox({ width:150, panelHeight: \'auto\', textField:\'label\', valueField:\'value\', editable:false, //editable 是否可以输入 data:[{ label:\'是\', value:\'true\' },{ label:\'否\', value:\'false\' }], onLoadSuccess:function () {/*数据加载完毕后回调显示提示*/ $("#state").each(function(i){ var span =$(this).siblings("span")[i]; var targetInput = $(span).find("input:first"); if(targetInput) { $(targetInput).attr("placeholder", $(this).attr("placeholder")); } }) } }); 12. 下拉列表部门数据加载 先建立一个controller DepartmentController @Controller public class DepartmentController { /*注入DepartmentService业务层*/ @Autowired private DepartmentService departmentService; //部门下拉查询部门 @RequestMapping("/departList") @ResponseBody public List<Department> departList(){ List<Department> departmentList = departmentService.getDepartmentList(); return departmentList; } } 创建它依赖的业务层接口DepartmentService 创建业务层DepartmentServiceImpl @Service public class DepartmentServiceImpl implements DepartmentService { /*注入mapper*/ @Autowired private DepartmentMapper departmentMapper; @Override public List<Department> getDepartmentList() { List<Department> departments = departmentMapper.selectAll(); return departments; } } 修改employee.jsp /*部门选择 下拉列表*/ $("#department").combobox({ width:150, panelHeight:\'auto\', editable:false, url:\'departList\', textField:\'name\', valueField:\'id\', onLoadSuccess:function () {/*数据加载完毕后回调显示提示*/ $("#department").each(function(i){ var span =$(this).siblings("span")[i]; var targetInput = $(span).find("input:first"); if(targetInput) { $(targetInput).attr("placeholder", $(this).attr("placeholder")); } }) } }); 13. 添加表单提交 修改对话框,添加两个按钮 /*对话框*/ $("#dialog").dialog({ width:350, height:350, closed:true, buttons:[{ /*提交表单*/ $("#employeeForm").form("submit",{ url:"saveEmployee", success:function(data){ console.log(data); } }); } },{ text:\'关闭\', handler:function() { } }] }); 给.jsp的对话框添加form表单属性 <%--对话框--%> <div id="dialog"> <form id="employeeForm"> </form> </div> 给对话框内的所有参数添加name属性 <td><input type="text" name="username" class="easyui-validatebox" data-options="required:true"></td> <td><input type="text" name="password" class="easyui-validatebox" data-options="required:true"></td> 在EmployeeController层添加接收表单 /*接收员工添加表单*/ @RequestMapping("/saveEmployee") public void saveEmployee(Employee employee){ System.out.println("提交表单成功"); System.out.println(employee); } 让后台可以接收到属性 14. 添加表单提交 由于前面设计表时没有添加密码,需要在数据库添加password字段 在controller层 /*接收员工添加表单*/ @RequestMapping("/saveEmployee") public void saveEmployee(Employee employee){ /*调用业务层,保存用户*/ employeeService.saveEmployee(employee); } service层 @Transactional /*保存员工*/ @Override public void saveEmployee(Employee employee) { employeeMapper.insert(employee); } 注意是添加,所以需要添加事务的注释. 修改mapper <!--添加员工--> <insert id="insert" parameterType="com.itlike.domain.Employee" > insert into employee (id, username, password, inputtime, tel, email, state, admin, dep_id) values (#{id}, #{username},#{password}, #{inputtime}, #{tel}, #{email}, #{state}, #{admin}, #{department.id}) </insert> 由于dep_id是从department.id获取的值,所以修改values.并且添加password字段. state还未设置,需要添加设置在service层 /*接收员工添加表单*/ @RequestMapping("/saveEmployee") public void saveEmployee(Employee employee){ /*调用业务层,保存用户*/ employee.setState(true); employeeService.saveEmployee(employee); } 添加一个domain类AjaxRes,用来接收参数. 用来表明接收失败还是成功,还有message. .parseJSON()函数 用于将格式完好的JSON字符串转为与之对应的JavaScript对象。 AjaxRes @Component @Getter@Setter@ToString public class AjaxRes { private boolean success; private String msg; } EmployeeController @Autowired private AjaxRes ajaxRes; /*接收员工添加表单*/ @RequestMapping("/saveEmployee") @ResponseBody public AjaxRes saveEmployee(Employee employee){ try { /*调用业务层,保存用户*/ employee.setState(true); employeeService.saveEmployee(employee); ajaxRes.setMsg("保存成功"); ajaxRes.setSuccess(true); }catch (Exception e){ ajaxRes.setSuccess(false); ajaxRes.setMsg("保存失败"); } return ajaxRes; } 修改employee.js success:function(data){ data = $.parseJSON(data); if(data.success){ $.messager.alert("温馨提示",data.msg); /*关闭对话框*/ $("#dialog").dialog("close"); /*重新加载数据表格*/ $("#dg").datagrid("reload"); }else { $.messager.alert("温馨提示",data.msg); } } 15. 编辑数据回显. 先添加关闭按钮的响应 text:\'关闭\', handler:function() { $("#dialog").dialog("close"); } 然后添加按钮的打开时清空. /*监听添加按钮点击*/ $("#add").click(function () { $("#employeeForm").form("clear"); 使用.form(clear) $("#dialog").dialog("open"); }); ----------------------------------------------------------------------- /*监听添加按钮点击*/ $("#add").click(function () { $("#dialog").dialog("setTitle","编辑员工"); /*清空对话框中的数据*/ $("#employeeForm").form("clear"); /*打开对话框*/ $("#dialog").dialog("open"); }); /*员工数据列表*/ $("#dg").datagrid({ singleSelect:true, /*只能选择一行*/ striped:true, /*斑马线显示,隔行变色*/} 如果需求是不能编辑密码,那么选择编辑时隐藏密码选项 在jsp页面给tr添加id为password <tr id="password"> <td>密码:</td> <td><input type="text" name="password" class="easyui-validatebox" data-options="required:true"></td> </tr> /*监听编辑按钮点击*/ $("#edit").click(function () { /*获取当前选中的行*/ var rowData = $("#dg").datagrid("getSelected"); console.log(rowData); if(!rowData){ $.messager.alert("提示","请选择一行数据进行编辑"); return; } /*隐藏密码选项*/ $("#password").hide(); /*弹出对话框*/ $("#dialog").dialog("setTitle","编辑员工"); $("#dialog").dialog("open"); /*设置部门,由于部门是department.id,所以需要设置,不能直接回调*/ rowData["department.id"] = rowData["department"].id; /*设置管理员回显*/ rowData["admin"]=rowData["admin"]+""; /*选中数据的回显*/ $("#employeeForm").form("load",rowData); }) 16. 编辑提交 由于是使用的同一个对话框dialog,所以在提交表单之前,需要判断是新增还是添加. 在jsp页面的form标签下添加一个隐藏域 jsp页面 <form id="employeeForm"> <%--添加一个隐藏域用于区分添加和编辑--%> <input type="hidden" name="id"> employee.js handler:function(){ /*判断当前是新增还是编辑*/ var id = $("[name=\'id\']").val(); /*由于要区分新增和编辑,下面url不能写死.*/ var url; if(id){ /*如果有id则为编辑操作*/ url = "updateEmployee" }else{ /*如果没有则为添加*/ url = "saveEmployee" } /*提交表单*/ $("#employeeForm").form("submit",{ url:url, 由于采用的对话框编辑和添加时同一个,而编辑没有密码的选项,但是密码属于data-options="required:true" 是必填项,所以需要取消密码校验. 在employee.js /*监听编辑按钮点击*/下添加 /*取消密码的验证*/ $("[name= \'password\']").validatebox({required:false}); //validatebox验证盒设置必需为否 /*隐藏密码选项*/ $("#password").hide(); 然后在添加时需要去增加密码验证 /*监听添加按钮点击*/ $("#add").click(function () { /*添加密码验证*/ $("[name=\'password\']").validatebox({required: true}); 在EmployeeController添加 @RequestMapping("/updateEmployee") /*编辑员工数据*/ @RequestMapping("/updateEmployee") public void updateEmployee(){ System.out.println("编辑员工数据"); } 17. 更新员工 EmployeeController /*编辑员工数据*/ @RequestMapping("/updateEmployee") @ResponseBody public AjaxRes updateEmployee(Employee employee){ try { employeeService.updateEmployee(employee); ajaxRes.setMsg("编辑成功"); ajaxRes.setSuccess(true); }catch (Exception e){ ajaxRes.setSuccess(false); ajaxRes.setMsg("编辑失败"); } return ajaxRes; } employeeService /*编辑员工*/ public void updateEmployee(Employee employee); employeeServiceImpl @Override public void updateEmployee(Employee employee) { /*调用业务层更新员工*/ employeeMapper.updateByPrimaryKey(employee); } 18. 设置离职状态 先监听按钮的点击 /*设置离职按钮的点击*/ $("#delete").click(function () { /*获取当前选中的行*/ var rowData =$("#dg").datagrid("getSelected"); console.log(rowData); if(!rowData){ $.messager.alert("请选择一行数据进行删除"); return; } /*提醒用户是否做离职操作*/ $.messager.confirm("确认","是否做离职操作",function (res) { if(res){ /*点击确认,返回值为true,点击取消返回值为false,做离职的操作*/ $.get("/updateState?id="+rowData.id,function (data) { /* data = $.parseJSON(data);由于前面时使用get方式提交,所有不再需要转换json格式*/ if(data.success){ $.messager.alert("温馨提示",data.msg); /*重新加载数据表格*/ $("#dg").datagrid("reload"); }else { $.messager.alert("温馨提示",data.msg); } }) } }) }); EmployeeController /*离职操作请求*/ @RequestMapping("/updateState") @ResponseBody public AjaxRes updateState(Long id){ try { /*设置员工离职状态*/ employeeService.updateState(id); ajaxRes.setMsg("删除成功"); ajaxRes.setSuccess(true); }catch (Exception e){ ajaxRes.setSuccess(false); ajaxRes.setMsg("删除失败"); } return ajaxRes; } employeeService /*离职员工*/ public void updateState(Long id); employeeServiceImpl @Override public void updateState(Long id) { /*员工离职状态*/ employeeMapper.updateState(id); } EmployeeMapper void updateState(Long id); EmployeeMapper.xml <!--设置员工离职状态--> <update id="updateState"> update employee set state=false where id=#{id} </update> 19. 离职按钮状态设置 需求,状态设置为离职后,选择该条数据不能再点击离职. employee.js/*员工数据列表*/下面最后添加 onClickRow:function (rowIndex,rowData) { /*判断当前行是否是离职状态*/ if(!rowData.state){ /*是离职,把离职按钮禁用*/ $("#delete").linkbutton("disable"); }else{ /*未离职,启用*/ $("#delete").linkbutton("enable"); } } 但是disable还是可以点击,esayUI的bug. 添加一个base.js覆盖重写linkbutton方法扩展 把base.js放在js目录下. 20. 分页添加 添加一个domain QueryVo接收页面的两个参数 由于前端页面设定的form data 有两个参数page:1 rows:10. QueryVo @Getter@Setter@ToString public class QueryVo { private int page; private int rows; } 在EmployeeController层 @RequestMapping("/employeeList") @ResponseBody public PageListRes employeeList(QueryVo vo){ /*调用业务层查询员工*/ PageListRes pageListRes = employeeService.getEmployee(vo); return pageListRes; } 传入一个vo参数. employeeService /*查询员工*/ public PageListRes getEmployee(QueryVo vo); employeeServiceImpl @Override public PageListRes getEmployee(QueryVo vo) { /*调用mapper查询员工*/ /*设置分页total*/ Page<Object> page = PageHelper.startPage(vo.getPage(), vo.getRows()); 就可以通过页面设置每页的数据条数,自动分页. pageList:[10,20,30,50], /*设置每页显示行数*/ 21. 搜索操作 在工具栏添加一个查询框 <input type="text" name="keyword" style="width: 200px; height: 30px;padding-left: 5px;"> <a class="easyui-linkbutton" iconCls="icon-search" id="searchbtn">查询</a> 监听按钮点击employee.js /*监听搜索按钮点击*/ $("#searchbtn").click(function () { /*获取搜索框内容*/ var keyword = $("[name=\'keyword\']").val(); /*重新加载列表 把参数传过去*/ $("#dg").datagrid("load",{keyword:keyword}); }) QueryVo中添加 private String keyword; employeeServiceImpl在查询中传入参数vo List<Employee> employees = employeeMapper.selectAll(vo); EmployeeMapper添加参数vo List<Employee> selectAll(QueryVo vo); EmployeeMapper.xml改写sql语句,引用sql片段 添加sql片段 <sql id="where_sql" > <where> <if test="keyword !=null and keyword!=\'\'"> and e.username like concat(\'%\',#{keyword},\'%\') or e.tel like concat(\'%\',#{keyword},\'%\') or e.email like concat(\'%\',#{keyword},\'%\') </if> </where> </sql> <!--员工关联部门查询--> <select id="selectAll" resultMap="BaseResultMap" > select e.id, e.username, e.inputtime, e.tel, e.email, e.state, e.admin, d.id as d_id, d.`name` as d_name from employee as e LEFT JOIN department as d ON e.dep_id=d.id <include refid="where_sql"/> //引用sql片段 order by e.id //根据id排序 </select> 22. 刷新操作 直接在js上添加一个刷新按钮的监控就行了,然后清空搜索框内容,再重载一下. /*监听刷新点击*/ $("#reload").click(function () { /*清空搜索内容*/ var keyword = $("[name=\'keyword\']").val(\'\'); /*重新加载数据*/ $("#dg").datagrid("load",{}); }) 23 角色权限关系表建立
建立角色与权限的表 为多对多关系 角色表 权限表 角色与权限中间表 使用代码生成器生成相关mapper 建立权限表permission,三个字段pid,pname,presource. pid为主键自增 建立角色表role,三个字段rid,rname,rnum. rid为主键自增 角色与权限中间表rid和pid为双主键 然后用代码逆向工程生成相关的domain,mapper,mapper.xml 24. 角色列表展示 前端创建role.jsp <body> <%--数据表格--%> <div id="role_dg"></div> </body> 创建JS role.js $(function () { /*角色数据列表*/ $("#role_dg").datagrid({ url:"/getRoles", columns:[[ {field:\'rnum\',title:\'角色编号\',width:100,align:\'center\'}, {field:\'rname\',title:\'角色名称\',width:100,align:\'center\'}, ]], fit:true, /*是否需要填满*/ fitColumns:true, /*是否自动伸缩*/ rownumbers:true, /*是否需要显示行号*/ pagination:true, /*是否需要分页*/ pageList:[10,20,30,50], /*设置每页显示行数*/ singleSelect:true, /*只能选择一行*/ striped:true, /*斑马线显示,隔行变色*/ /* toolbar:"#tb", /!*添加工具栏标签*!/*/ }); }); 前端控制器 RoleController /*注入业务层*/ @Autowired private RoleService roleService; @RequestMapping("/role") public String Role(){ return "role"; } /*接收请求角色列表*/ @RequestMapping("/getRoles") @ResponseBody public PageListRes getRoles(QueryVo vo){ /*调用业务层 查询角色列表*/ PageListRes roles = roleService.getRoles(vo); return roles; } RoleService public interface RoleService { /*查询角色列表*/ public PageListRes getRoles(QueryVo vo); } RoleServiceImpl @Service public class RoleServiceImpl implements RoleService { /*注入mapper*/ @Autowired private RoleMapper roleMapper; @Override public PageListRes getRoles(QueryVo vo) { /*调用mapper查询数据*/ Page<Object> page = PageHelper.startPage(vo.getPage(), vo.getRows()); List<Role> roles = roleMapper.selectAll(); /*封装成pageList*/ PageListRes pageListRes = new PageListRes(); pageListRes.setTotal(page.getTotal()); pageListRes.setRows(roles); return pageListRes; } } 25. 添加角色对话框 添加工具栏 <%--工具栏--%> <div id="toolbar"> <a href="#" class="easyui-linkbutton" data-options="iconCls:\'icon-add\',plain:true" id="add">添加</a> <a href="#" class="easyui-linkbutton" data-options="iconCls:\'icon-edit\',plain:true" id="edit">编辑</a> <a href="#" class="easyui-linkbutton" data-options="iconCls:\'icon-remove\',plain:true" id="remove">删除</a> </div> 添加对话框 <%--添加/编辑对话框--%> <div id="dialog"> <form id="myform"> <table align="center" style="border-spacing: 20px 30px"> <input type="hidden" name="id"> <tr align="center"> <td>角色编号:<input type="text" name="username" class="easyui-validatebox"></td> <td>角色名称:<input type="text" name="username" class="easyui-validatebox"></td> </tr> <tr> <td><div id="role_data1"></div></td> <td><div id="role_data2"></div></td> </tr> </table> </form> </div> 修改role.js /*添加编辑对话框*/ $("#dialog").dialog({ width:500, height:550 }); /*添加角色*/ $("#add").click(function () { }); /*权限列表*/ $("#role_data1").datagrid({ title:"所有权限", width:200, height:300, fitColumns: true, columns: [[ {field:\'pname\',title:\'权限名称\',width:100,align:\'center\'} ]] }); /*选中权限*/ $("#role_data2").datagrid({ title:"已选权限", width:200, height:300, fitColumns: true, columns: [[ {field:\'pname\',title:\'权限名称\',width:100,align:\'center\'} ]] }); 26. 修改EasyUI默认样式 <style> #dialog #myform .panel-header{ height:20px; } #dialog #myform .panel-title{ color: black; margin-top: -5px; text-align: center; } </style> 单独对某部分的样式进行修改,复写css样式 27. 加载权限列表 /*权限列表*/ $("#role_data1").datagrid({ title:"所有权限", width:200, height:300, fitColumns: true, singleSelect:true, url:\'/permissionList\', columns: [[ {field:\'pname\',title:\'权限名称\',width:100,align:\'center\'} ]] }); 新建一个PermissionController @Controller public class PermissionController { /*注入业务*/ @Autowired private PermissionService permissionService; @RequestMapping("/permissionList") @ResponseBody public List<Permission> permissionList(){ List<Permission> permission = permissionService.getPermission(); return permission; } } PermissionService public interface PermissionService { public List<Permission> getPermission(); } PermissionServiceImpl @Service public class PermissionServiceImpl implements PermissionService { @Autowired private PermissionMapper permissionMapper; @Override public List<Permission> getPermission() { List<Permission> permissions = permissionMapper.selectAll(); return permissions; } } 由于权限选项不多,不用做分页,直接使用list<permission>接收值,不用使用pageListRes 28. 添加权限. 给第一个权限列表添加点击响应 /*权限列表*/ $("#role_data1").datagrid({ onClickRow:function (rowIndex,rowData) {/*点击一行时回调*/ /*判断是否已经存在该权限*/ /*取出所有的已选权限*/ var allRows = $("#role_data2").datagrid("getRows"); /*取出每一个进行判断*/ for(var i=0;i<allRows.length;i++){ /*取出每一行*/ var row = allRows[i]; if(rowData.pid == row.pid){/*已经存在该权限*/ /*让已经存在权限称为选中状态*/ /*获取已经称为选中状态当前角标*/ var index = $("#role_data2").datagrid("getRowIndex",row); /*让该行成为选中状态*/ $("#role_data2").datagrid("selectRow",index); return; } } /*把当前选中的,添加到已选权限*/ $("#role_data2").datagrid("appendRow",rowData); }} }); 给第二个选中权限添加点击响应 /*已经选中的权限*/ $("#role_data2").datagrid({ onClickRow:function (rowIndex,rowData) { /*是否删除当前选中的行*/ $("#role_data2").datagrid("deleteRow",rowIndex); } }); 29. 保存角色提交 role.js里添加保存和关闭按钮 /*添加编辑对话框*/ $("#dialog").dialog({ width:500, height:550, buttons:[{ text: \'保存\', handler: function () { /*提交表单*/ $("#myform").form("submit",{ url:"saveRole", onSubmit:function(param){ /*传递额外参数,已选择的权限*/ /*获取已选权限*/ var allRows = $("#role_data2").datagrid("getRows"); /*遍历出每一个权限*/ for(var i=0; i<allRows.length; i++){ /*取出每一个权限*/ var row = allRows[i]; /*封装到集合中*/ param["permissions["+i+"].pid"] =row.pid; } }, success:function (data) { } }); } },{ text:\'关闭\', handler:function () { $("#dialog").dialog("close"); } }] }); RoleController里添加 /*接收 保存角色请求地址*/ @RequestMapping("/saveRole") @ResponseBody public void saveRole(Role role){ System.out.println(role); } 由于role还需要接收权限所有修改Role.java添加一个参数,使用一个ArrayList来接收权限. /*一个角色可以有多个权限*/ private List<Permission> permissions =new ArrayList<>(); 关键点学习使用onSubmit:function(param){ /*传递额外参数*/传递额外的参数.} 30. 保存角色与权限 RoleController.java里,使用roleService.saveRole方法进行保存 /*接收 保存角色请求地址*/ @RequestMapping("/saveRole") @ResponseBody public void saveRole(Role role){ /*调用业务层,保存角色和权限*/ roleService.saveRole(role); } RoleService /*保存角色和权限*/ public void saveRole(Role role); @Transactional RoleServicelmpl /*保存角色和权限*/ @Override public void saveRole(Role role) { /*1.保存角色*/ roleMapper.insert(role); /*rid:3 pid:1,2*/ /*2.保存权限之间的关系*/ for (Permission permission : role.getPermissions()) { roleMapper.insertRoleAndPermission(role.getRid(),permission.getPid()); } } RoleMapper.java 记得使用@Param不然只能使用param1,param2. /* 保存角色与权限关系*/ void insertRoleAndPermission(@Param("rid") Long rid, @Param("pid") Long pid); 修改RoleMapper.xml添加一个useGeneratedKeys="true"获取生成的key 和keyProperty="rid" 赋值给哪个属性 <!--保存角色--> <insert id="insert" parameterType="com.itlike.domain.Role" useGeneratedKeys="true" keyProperty="rid"> insert into role (rid, rnum, rname) values (#{rid,jdbcType=BIGINT}, #{rnum,jdbcType=VARCHAR}, #{rname,jdbcType=VARCHAR}) </insert> <!--保存角色与权限关系--> <insert id="insertRoleAndPermission"> insert into role_permission_rel(rid,pid)values (#{rid},#{pid}) </insert> 再在Controller层注入 /*注入一个AjaxRes类*/ @Autowired private AjaxRes ajaxRes; 添加一个ajaxRes回调提示成功与否. /*接收 保存角色请求地址*/ @RequestMapping("/saveRole") @ResponseBody public AjaxRes saveRole(Role role){ try { /*调用业务层,保存角色和权限*/ roleService.saveRole(role); ajaxRes.setMsg("保存成功"); ajaxRes.setSuccess(true); }catch (Exception e){ ajaxRes.setMsg("保存失败"); ajaxRes.setSuccess(false); } return ajaxRes; } 在js页面提示数据转换成json格式然后提示并重载 success:function (data) { data=$.parseJSON(data) if(data.success){ $.messager.alert("温馨提示",data.msg); /*关闭对话框*/ $("#dialog").dialog("close"); /*重载数据表格*/ $("#role_dg").datagrid("reload"); }else { $.messager.alert("温馨提示",data.msg); } } 添加一个 closed:true,让对话框开始为关闭,点击时才打开. 31. 角色编辑回显 让对话框添加时为空,而编辑角色时,才有回显. 修改role.js,让添加时为空 /*添加角色*/ $("#add").click(function () { /*清空表单*/ $("#myform").form("clear"); /*清空已选权限*/ $("#role_data2").datagrid("loadData",{rows:[]}); /*设置标题*/ $("#dialog").dialog("setTitle","添加角色"); /*打开对话框*/ $("#dialog").dialog("open"); }); role.js 让编辑时回显. /*监听编辑按钮*/ $("#edit").click(function () { var rowData = $("#role_dg").datagrid("getSelected"); console.log(rowData); if(!rowData){ $.messager.alert("提示","选择一行数据进行编辑"); return; } /*回显表单*/ $("#myform").form("load",rowData); /*设置标题*/ $("#dialog").dialog("setTitle","编辑角色"); /*打开对话框*/ $("#dialog").dialog("open"); }); 32. 角色权限回显 前面只实现了角色编号和角色名称的回显,本次需要实现角色已选权限的回显. role.js /*监听编辑按钮*/ $("#edit").click(function () { var rowData = $("#role_dg").datagrid("getSelected"); console.log(rowData); if(!rowData){ $.messager.alert("提示","选择一行数据进行编辑"); return; } /*加载当前角色的权限*/ var options = $("#role_data2").datagrid("options"); options.url="/getPermissionByRid?rid="+rowData.rid; /*重新加载数据*/ $("#role_data2").datagrid("load"); /*回显表单*/ $("#myform").form("load",rowData); /*设置标题*/ $("#dialog").dialog("setTitle","编辑角色"); /*打开对话框*/ $("#dialog").dialog("open"); }); 在控制器PermissionController写对应的requestMapping /*根据角色查询对应权限*/ @RequestMapping("/getPermissionByRid") @ResponseBody public List<Permission> getPermissionByRid(Long rid){ System.out.println(rid); List<Permission> permissions= permissionService.getPermissionByRid(rid); return permissions; } service层PermissionService /*根据角色查询对应的权限*/ public List<Permission> getPermissionByRid(Long rid); impl PermissionServiceImpl /*根据角色查询对应的权限*/ @Override public List<Permission> getPermissionByRid(Long rid) { List<Permission> permissions = permissionMapper.selectPermissionByRid(rid); return permissions; } Mapper /*根据角色查询对应的权限*/ List<Permission> selectPermissionByRid(Long rid); PermissionMapper.xml <!--根据角色查询对应的权限--> <select id="selectPermissionByRid" resultType="com.itlike.domain.Permission"> SELECT pid,pname,presource from permission where pid in(SELECT pid FROM role_permission_rel where rid=#{rid}); </select> 33. 更新角色权限 在role.js中保存按钮时,判断它到底是保存还是编辑 buttons:[{ text: \'保存\', handler: function () { /*判断当前是保存还是编辑*/ var rid = $("[name=\'rid\']").val(); var url; if(rid){ /*如果rid有值则是编辑操作*/ url="updateRole"; }else { /*如果rid没值则是保存操作*/ url="saveRole"; } /*提交表单*/ $("#myform").form("submit",{ url:url, controller层 /*更新操作*/ @RequestMapping("/updateRole") @ResponseBody public AjaxRes updateRole(Role role){ /*调用更新角色业务*/ try { roleService.updateRole(role); ajaxRes.setMsg("更新成功"); ajaxRes.setSuccess(true); }catch (Exception e){ ajaxRes.setSuccess(false); ajaxRes.setMsg("保存失败"); } return ajaxRes; } service /*更新角色*/ void updateRole(Role role); impl实现类 /*更新角色*/ @Override public void updateRole(Role role) { /*先打破角色与权限的关系,先删除*/ roleMapper.deletePermissionRel(role.getRid()); /*更新角色*/ roleMapper.updateByPrimaryKey(role); /*重新建立与权限的关系*/ for (Permission permission : role.getPermissions()) { roleMapper.insertRoleAndPermission(role.getRid(),permission.getPid()); } } mapper /*先打破角色与权限的关系,先删除*/ void deletePermissionRel(Long rid); mapper.xml <!--先打破角色与权限的关系,先删除--> <delete id="deletePermissionRel"> delete from role_permission_rel where rid=#{rid}; </delete> 34. 删除角色 role.js监听删除按钮 /*监听删除点击*/ $("#remove").click(function () { /*获取当前选中行*/ var rowData = $("#role_dg").datagrid("getSelected"); console.log(rowData); if(!rowData){ $.messager.alert("提示","选择一行数据进行删除") return; } /*提示一次是否确认删除*/ $.messager.confirm("确认","是否删除",function (res) { if(res) { $.get("/deleteRole?rid=" + rowData.rid, function (data) { if (data.success) { $.messager.alert("温馨提示", data.msg); /*关闭对话框*/ $("#dialog").dialog("close"); /*重载数据表格*/ $("#role_dg").datagrid("reload"); } else { $.messager.alert("温馨提示", data.msg); } }) } }); }) roleController /*删除操作*/ @RequestMapping("/deleteRole") @ResponseBody public AjaxRes deleteRole(Long rid){ /*删除角色业务*/ try{ roleService.deleteRole(rid); ajaxRes.setMsg("删除成功"); ajaxRes.setSuccess(true); }catch (Exception e){ ajaxRes.setSuccess(false); ajaxRes.setMsg("删除失败"); } return ajaxRes; } roleService /*删除角色*/ void deleteRole(Long rid); roleServiceImpl /*删除角色*/ @Override public void deleteRole(Long rid) { /*删除关联权限*/ roleMapper.deletePermissionRel(rid); /*删除角色*/ roleMapper.deleteByPrimaryKey(rid); } 35. 添加员工角色加载 employee.jsp添加一行 <tr> <td>选择角色:</td> <td><input id="role" name="role.rid" placeholder="选择角色"></td> </tr> employee.js /*选择角色 下拉列表*/ $("#role").combobox({ width:150, panelHeight:\'auto\', editable:false, url:\'roleList\', textField:\'rname\', valueField:\'rid\', multiple:true,/*是否支持多选*/ onLoadSuccess:function () {/*数据加载完毕后回调显示提示*/ $("#role").each(function(i){ var span =$(this).siblings("span")[i]; var targetInput = $(span).find("input:first"); if(targetInput) { $(targetInput).attr("placeholder", $(this).attr("placeholder")); } }) } }); roleController /*获取角色列表 在employee中使用*/ @RequestMapping("/roleList") @ResponseBody public List<Role> roleList(){ return roleService.roleList(); } roleService /*查询角色列表,不做分页*/ List<Role> roleList(); roleServiceImpl @Override public List<Role> roleList() { return roleMapper.selectAll(); } 36. 保存员工角色关系 先让选择角色支持多选 multiple:true,/*是否支持多选*/ 设置一个员工角色关系表,eid和rid为双主键 包含eid,rid,eid关联employee的id,rid关联role的rid. 在Employee中添加相关字段. private List<Role> roles = new ArrayList<>();; 修改employee.js /*提交表单*/ $("#employeeForm").form("submit",{ url:url, onSubmit:function(param){/*添加额外参数*/ /*获取选中角色*/ var values = $("#role").combobox("getValues"); for(var i=0;i<values.length;i++){ var rid = values[i]; param["roles["+i+"].rid"] =rid; } }, 给EmployeeMapper.xml的保存员工添加参数 <!--添加员工--> <insert id="insert" parameterType="com.itlike.domain.Employee" useGeneratedKeys="true" keyProperty="id"> insert into employee (id, username, password, inputtime, tel, email, state, admin, dep_id) values (#{id}, #{username},#{password}, #{inputtime}, #{tel}, #{email}, #{state}, #{admin}, #{department.id}) </insert> 添加useGeneratedKeys和keyProperty 修改employeeServiceImpl,添加保存角色关系表 /*保存员工*/ @Override public void saveEmployee(Employee employee) { /*保存员工*/ employeeMapper.insert(employee); /*保存角色关系表*/ for (Role role : employee.getRoles()) { employeeMapper.insertEmployeeAndRoleRel(employee.getId(),role.getRid()); } } EmployeeMapper /*保存员工和角色关系*/ void insertEmployeeAndRoleRel(@Param("id") Long id, @Param("rid") Long rid); EmployeeMapper.xml <!--保存员工和角色 关系--> <insert id="insertEmployeeAndRoleRel"> insert into employee_role_rel(eid,rid) values (#{id},#{rid}); </insert> 37. 编辑员工角色回显 在编辑按钮中设置回显employee.js /*设置角色的回显 根据当前用户id查询当前角色id*/ $.get("/getRoleByEid?id="+rowData.id,function (data) { /*设置下拉列表数据回显*/ $("#role").combobox("setValues",data); }) RoleController /*根据用户id查询对应的角色*/ @RequestMapping("/getRoleByEid") @ResponseBody public List<Long> getRoleByEid(Long id){ return roleService.getRoleByEid(id); } RoleService /*根据用户id查询对应的角色*/ List<Long> getRoleByEid(Long id); RoleServiceImpl /*根据用户id查询对应的角色*/ @Override public List<Long> getRoleByEid(Long id) { return roleMapper.getRoleWithId(id); } RoleMapper /*根据用户id查询对应的角色*/ List<Long> getRoleWithId(Long id); RoleMapper.xml <!-- 根据用户id查询对应的角色 --> <select id="getRoleWithId" resultType="java.lang.Long"> select rid from employee_role_rel where eid=#{id}; </select> 38. 编辑员工角色更新 修改serviceImpl层 /*编辑员工*/ @Override public void updateEmployee(Employee employee) { /*打破和角色间的关系*/ employeeMapper.deleteRoleRel(employee.getId()); /*调用业务层更新员工*/ employeeMapper.updateByPrimaryKey(employee); /*更新角色与员工的关系*/ for (Role role : employee.getRoles()) { employeeMapper.insertEmployeeAndRoleRel(employee.getId(),role.getRid()); } } 添加Mapper /*打破角色与员工的关系*/ void deleteRoleRel(Long id); Mapper.xml <!--打破员工与角色关系--> <delete id="deleteRoleRel"> delete from employee_role_rel where eid=#{id} </delete>
39. Shiro概述 apache shiro Java的安全框架 轻量级 spring中有spring security,但是过于依赖spring,没有shiro使用简单 权限管理实现用户访问系统的控制 用户可以访问而且只能访问自己被授权的资源 只要有用户和密码的系统,权限管理几乎都会出现 权限管理分为 登录认证:对于需要访问控制的资源用,首先经过身份认证 判断一个用户是否为合法用户的处理过程. 授权:认证通过用户具有资源的访问权限-方可访问 控制能够访问哪些资源 shrio概述 Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。 使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。 40. Shiro核心概念 核心类: Authentication(认证) 身份认证/登录,验证用户是否拥有相应身份 Authorization(授权) 授权,即授权验证,验证某个已认证用户是否拥有某个权限 前两个是重点 Session Manager 会话管理,用户登录后就是一次会话,没有退出前,所有信息都在会话里 Cryptography 加密,保护数据安全 Web Support Web支持,可以非常容易的集成到web环境 Caching 缓存,比如用户登录后,其用户信息,拥有权限/角色不用每次检查,提高效率 Concurrency shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去. Testing 提供测试支持 Run As 允许一个用户假装另外一个用户(如果他们允许)的身份进行访问 Remember Me 记住我,这个是非常常见的功能,即一次登录后,下次再来不用登录了 主要概念: Subject: 当前的操作用户:可以是人,爬虫,当前跟软件交互的东西 在shiro当中我们可以统称"用户" 在代码的任何地方,你都能轻易的获取shiro Subject. 一旦获取subject,你就可以立即获得你希望用shiro为当前用户做的90%的事情 SecurityManager: SecurityManager则管理所有用户的安全操作 引用了多个内部嵌套安全组件,是shiro框架的核心 你可以把它看成DispatcherServlet前端控制器(springmvc) 用于调度各种shiro框架的服务 Realms: realms则是用户的信息认证器和用户的权限认证器 执行认证(登录)和授权(访问控制)时,shiro会从应用配置的realm中查找很多内容 realm可以理解为读取用户信息,角色及权限的DAO SecurityManager要验证用户身份和权限,那么它需要从Realm获取相应的信息进行比较以确定用户身份是否合法 可以将Realm看成dateSource,即安全数据源. Shiro框架: subject主体: 主体可以是程序,主体要访问系统,系统需要对主体进行认证,授权 securityManager安全管理器: authenticator认证器: authorizer授权器: sessionManager会话管理 sessionDao: cacheManager缓存管理器 realm领域 cryptography密码管理: 41. Shiro工程依赖添加 什么是认证: 身份认证,就是判断一个用户是否为合法用户的处理过程 通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确 关键对象: subject主体 用户 Principal 身份信息 是主体Subject进行身份认证的标识,标识必须具有唯一性,如用户名,手机号,邮箱地址等 credential 凭证信息 是只有主体自己知道的安全信息,如密码,证书等. 使用ini完成认证: 1.在Maven中添加依赖jar包 2.添加shiro.ini配置文件 3.登录与退出 创建一个shiro项目 maven quickstart 添加shiro相关依赖 <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.4.0</version> </dependency> <!-- https://mvnrepository.com/artifact/commons-logging/commons-logging --> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-nop</artifactId> <version>1.7.24</version> </dependency> 添加ini文件 [users] itlike=1234 my=1234 42. shiro认证 public static void main( String[] args ) { /*1.构建securityManager工厂 ini 加载配置*/ IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini"); /*2.通过工厂创建securityManager*/ SecurityManager securityManager = factory.getInstance(); /*3.将securityManager设置到运行环境中*/ SecurityUtils.setSecurityManager(securityManager); /*4.创建一个Subject实例*/ Subject subject = SecurityUtils.getSubject(); /*5.创建token令牌*/ UsernamePasswordToken token = new UsernamePasswordToken("itlike", "1234"); /*6.用户登录*/ try { subject.login(token); }catch (UnknownAccountException e) { System.out.println("账号不存在"); e.printStackTrace(); }catch (IncorrectCredentialsException e){ System.out.println("密码错误"); e.printStackTrace(); } /*如果成功为true,反之为false*/ System.out.println("是否认证成功"+subject.isAuthenticated()); /*7.用户退出*/ subject.logout(); System.out.println("是否认证成功"+subject.isAuthenticated()); } 43 Shiro认证流程源码分析 构造SecurityManager环境 Subject.login()提交认证 SecurityManage.login()执行认证 Authentictor执行认证 Realm根据身份获取认证信息 1.调用subject.login方法进行登录,其会自动委托给securityManager.login方法进行登录; 2.securityManager通过Authenticator(认证器)进行认证; 3.Authenticator的实现ModularRealmAuthenticator调用realm从ini配置文件取用户真实的账号和密码 4.IniRealm先根据token中的账号去ini中找该账号,如果找不到则给ModularRealmAuthenticator返回null,如果找到则匹配密码,匹配密码成功则认证通过。 5、最后调用Subject.logout进行退出操作。 44. Shiro自定义realm 1.创建一个类继承AuthorizingRealm 2.覆盖doGetAuthenticationInfo方法,在此方法当中数据库获取用户,交有验证器去验证 public class MyRealm extends AuthorizingRealm { /*认证*/ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { /*判断当前用户是否存在*/ /*获取用户名*/ String username = (String) token.getPrincipal(); /*从数据库查出用户名和密码 真实需要从数据库取*/ String name="itlike"; String password="123456"; if(!name.equals(username)){ return null; } SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, password, this.getName()); return info; } /*授权*/ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { return null; } } 3.在ini文件当中进行配置 myRealm=com.itlike.MyRealm securityManager.realms=$myRealm 就可以在前面的app实例中使用自己的realm了. 45. Shiro散列密码 散列密码 概述 散列算法一般用于生成数据的摘要信息,是一种不可逆的算法 一般适合存储密码之类的数据,常见的散列算法如MD5、SHA等 使用shiro进行散列密码 Md5Hash SimpleHash Md5Hash 示例 public static void main(String[] args){ /*散列密码 每次加密结果一致*/ /*itlike是加盐值,3是3次散列*/ Md5Hash md5Hash = new Md5Hash("1234","itlike",3); System.out.println(md5Hash); /*使用SimpleHash*/ SimpleHash simpleHash = new SimpleHash("md5","1234","itlike",3); System.out.println(simpleHash); } 46. Shiro认证添加散列密码 realm中配置散列 在ini文件当中进行散列 [main] #定义凭证匹配器 credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher #散列算法 credentialsMatcher.hashAlgorithmName=md5 #散列次数 credentialsMatcher.hashIterations=3 #指定realm myRealm=com.itlike.MyRealm #配置散列 myRealm.credentialsMatcher=$credentialsMatcher #配置自定义散列 securityManager.realms=$myRealm 要保证数据库中的密码是经过散列之后的 还需要在MyRealm中添加盐值 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { /*判断当前用户是否存在*/ /*获取用户名*/ String username = (String) token.getPrincipal(); /*从数据库查出用户名和密码 真实需要从数据库取*/ String name="itlike"; String password="c4d626db1e96e4c058c3f088e9f2fc13"; if(!name.equals(username)){ return null; } SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, password, ByteSource.Util.bytes("itlike"), this.getName()); return info; } 在SimpleAuthenticationInfo中添加ByteSource.Util.bytes("itlike")盐值为itlike 47. Shiro授权 什么是授权 授权,即访问控制,控制谁能访问哪些资源。 主体进行身份认证后需要分配权限,方可访问系统的资源,对于某些资源没有权限是无法访问的。 使用ini形式配置权限信息 在ini文件中用户、角色、权限的配置规则 用户名=密码,角色1,角色2... 首先根据用户名找角色,再根据角色找权限,角色是权限集合。 权限字符串的规则 “资源标识符:操作:资源实例标识符” 对哪个资源的哪个实例具有什么操作 :”是资源/操作/实例的分割符 权限字符串也可以使用*通配符 新建一个shiro-permission.ini [users] #用户itlike的密码是1234,此用户具有role1和role2两个角色 itlike=1234,role1,role2 myxq=1234,role2 [roles] #角色role1对资源user拥有create、update权限 role1=user:create,user:update #角色role2对资源user拥有create、delete权限 role2=user:create,user:delete #角色role3对资源user拥有create权限 role3=user:create,user:edit 授权实例: public static void main( String[] args ) { /*1.构建securityManager工厂 ini 加载配置*/ IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro-permission.ini"); /*2.通过工厂创建securityManager*/ SecurityManager securityManager = factory.getInstance(); /*3.将securityManager设置到运行环境中*/ SecurityUtils.setSecurityManager(securityManager); /*4.创建一个Subject实例*/ Subject subject = SecurityUtils.getSubject(); /*5.创建token令牌*/ UsernamePasswordToken token = new UsernamePasswordToken("itlike", "1234"); /*6.用户登录*/ try { subject.login(token); }catch (UnknownAccountException e) { System.out.println("账号不存在"); e.printStackTrace(); }catch (IncorrectCredentialsException e){ System.out.println("密码错误"); e.printStackTrace(); } /*如果成功为true,反之为false*/ System.out.println("是否认证成功"+subject.isAuthenticated()); /*认证成功之后才做授权*/ /*判断当前用户是否有某一个角色或者权限*/ boolean role1 = subject.hasRole("role1"); /*判断当前用户是否有某一个角色或者权限*/ System.out.println("判断当前用户是否拥有权限1:"+subject.hasRole("role1")); System.out.println("判断当前用户是否拥有权限2:"+subject.hasRole("role2")); System.out.println("判断当前用户是否拥有权限3:"+subject.hasRole("role3")); /*判断当前用户是否同时具备多个角色 必须同时具备才显为true*/ System.out.println(subject.hasAllRoles(Arrays.asList("role1","role2","role3"))); /*判断是否有某一个权限*/ System.out.println(subject.isPermitted("user:create")); /*判断是否有多个权限*/ System.out.println(subject.isPermittedAll("user:delete","user:update","user:create")); } 48. Shiro自定义realm授权 修改MyRealm.java 重写它的AuthorizationInfo的返回值,手动赋值给它 /*授权*/ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { /*获取当前的身份信息*/ Object primaryPrincipal = principalCollection.getPrimaryPrincipal(); /*假设 用户的角色 授权*/ ArrayList<String> roles=new ArrayList<>(); roles.add("role1"); roles.add("role2"); ArrayList<String> permissions = new ArrayList<>(); permissions.add("user:create"); permissions.add("user:delete"); /*把角色和权限给添加添加到授权当中*/ SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.addRoles(roles); info.addStringPermissions(permissions); return info; } 在App中测试可用. 49. 整合web工程认证校验 拷贝一个登陆页面login.jsp 需求,需要认证后才能登陆index.jsp,否则只能在login.jsp 添加shiro的pom相关依赖 <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-nop</artifactId> <version>1.7.24</version> </dependency> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency> 1.登录拦截,如果没有登录,跳转到登录页面 1.在web.xml当中配置过滤器拦截所有请求,进行处理 <!-- 拦截到所有请求,使用spring一个bean来进行处理 --> <filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <!-- 是否filter中的init和 destroy--> <init-param> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> 2.在spring当中配置shiro过滤器和安全管理器 在resources中新建application-shiro.xml 然后在applicationContext中导入配置文件 <!--导入shiro--> <import resource="classpath:application-shiro.xml"/> application-shiro.xml中配置shiro过滤器 <!-- 配置shiro过滤器 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"></property> <!-- 配置shiro过滤器pattern --> <property name="filterChainDefinitions"> <value> /static/** = anon <!--不需要登录验证--> //anon就是不需要验证可以匿名登陆 /login.jsp = anon <!--不需要登录验证--> /**=authc <!--除指定请求外,其它所有的请求都需要身份验证--> //authc就是需要验证 /**就是除了以上全部需要 </value> </property> </bean> <!-- 配置shiro安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"></bean> 50. 配置loginUrl认证路径 由于scrpit比较少直接写在login.jsp中 <script> $(function () { $("#loginBtn").click(function () { /*发送请求,做登录认证*/ $.post("/login",$("form").serialize(),function (data) { console.log(data) }) }) }) </script> 配置application-shiro <!-- 配置shiro过滤器 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!-- index 其它的请求 会判断 当前有没有认证过 默认情况 ,没有认证,会跳转到login.jsp 如果配置了 loginUrl 没有认证 执行对应的login请求 login loginUrl:如果发现请求是loginUrl值 会去做认证 配置登录认证的路径 --> <property name="loginUrl" value="/login"/> <property name="securityManager" ref="securityManager"></property> <!-- 配置shiro过滤器pattern --> <property name="filterChainDefinitions"> <value> /static/** = anon <!--不需要登录验证--> /login.jsp = anon <!--不需要登录验证--> /**=authc <!--除指定请求外,其它所有的请求都需要身份验证--> </value> </property> </bean> 51. 配置员工realm 在web下创建一个新的包realm 创建一个新的类EmployeeRealm 继承AuthorizingRealm 重写它的认证方法AuthorizationInfo和授权方法AuthenticationInfo 然后在application_shiro.xml中配置realm <!--自定义realm--> <bean id="employeeRealm" class="com.itlike.web.realm.EmployeeRealm"></bean> <!-- 配置shiro安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <!--注入realm--> <property name="realm" ref="employeeRealm"/> </bean> 修改认证EmployeeRealm public class EmployeeRealm extends AuthorizingRealm { @Autowired private employeeService employeeService; /*认证*/ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("来到认证------"); /*获取身份信息*/ String username=(String)token.getPrincipal(); System.out.println(username); /*到数据库查询是否有当前用户*/ Employee employee = employeeService.getEmployeeWithUserName(username); if (employee == null){ return null; } /*认证*/ /*参数:主体,正确的密码,加的盐值*/ SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(employee,employee.getPassword(),this.getName()); return null; } } 52 登录用户查询 employeeService.getEmployeeWithUserName(username);方法的实现 serviceimpl层 @Override public Employee getEmployeeWithUserName(String username) { return employeeMapper.getEmployeeWithUserName(username); } mapper层 <!--根据用户名查询--> <select id="getEmployeeWithUserName" resultType="com.itlike.domain.Employee"> select * from employee where username=#{username} </select> 53. 监听登录认证结果 在web层添加一个过滤器 MyFormFilter 继承一个FormAuthenticationFilter 方法,重写它的onLoginSuccess和onLoginFailure public class MyFormFilter extends FormAuthenticationFilter { /*当认证成功时,调用*/ protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception { /*响应给浏览器*/ System.out.println("认证成功"); return false; } /*认证失败时,调用*/ protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) { /*响应给浏览器*/ System.out.println("认证失败"); return false; } } 在application-shiro.xml中配置 先将过滤器交给spring来管理 <!-- 配置过滤器--> <bean id="myFormFilter" class="com.itlike.web.filter.MyFormFilter"/> 然后它添加到shiro过滤器中去 <!-- 配置shiro过滤器 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!-- index 其它的请求 会判断 当前有没有认证过 默认情况 ,没有认证,会跳转到login.jsp 如果配置了 loginUrl 没有认证 执行对应的login请求 login loginUrl:如果发现请求是loginUrl值 会去做认证 配置登录认证的路径 --> <property name="loginUrl" value="/login"/> <!--配置表单监听的过滤器--> <property name="filters"> <map> <entry key="authc" value-ref="myFormFilter"/> </map> </property> <property name="securityManager" ref="securityManager"></property> <!-- 配置shiro过滤器pattern --> <property name="filterChainDefinitions"> <value> /static/** = anon <!--不需要登录验证--> /login.jsp = anon <!--不需要登录验证--> /**=authc <!--除指定请求外,其它所有的请求都需要身份验证--> </value> </property> </bean> 54. 登录成功处理. *Ajax发送请求,做登录认证,是没办法跳转服务器当中的请求只能通过在浏览器当中来做跳转 前端的js修改 $(function () { $("#loginBtn").click(function () { /**Ajax发送请求,做登录认证,是没办法跳转服务器当中的请求 * 只能通过在浏览器当中来做跳转*/ $.post("/login",$("form").serialize(),function (data) { /*把data json格式的字符串 转为json数据*/ data = $.parseJSON(data); if (data.success) { /*成功跳转到首页*/ window.location.href ="/index.jsp" }else { } }) }) }) MyFormFilter /*当认证成功时,调用*/ protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception { /*响应给浏览器*/ response.setCharacterEncoding("utf-8"); System.out.println("认证成功"); AjaxRes ajaxRes = new AjaxRes(); ajaxRes.setSuccess(true); ajaxRes.setMsg("登录成功"); /*把对象转成json格式字符串*/ String jsonString = new ObjectMapper().writeValueAsString(ajaxRes); response.getWriter().print(jsonString); return false; } 55. 登录失败处理 MyFormFilter /*认证失败时,调用*/ protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) { /*响应给浏览器*/ System.out.println("认证失败"); AjaxRes ajaxRes = new AjaxRes(); ajaxRes.setSuccess(false); if (e!=null){ /*获取异常名称*/ String name = e.getClass().getName(); if (name.equals(UnknownAccountException.class.getName())){ /*没有账号*/ ajaxRes.setMsg("账号错误"); }else if (name.equals(IncorrectCredentialsException.class.getName())){ /*密码错误*/ ajaxRes.setMsg("密码错误"); }else { ajaxRes.setMsg("未知异常"); /*未知异常*/ } } try { /*把对象转成json格式字符串*/ String jsonString = new ObjectMapper().writeValueAsString(ajaxRes); response.setCharacterEncoding("utf-8"); response.getWriter().print(jsonString); } catch (IOException e1) { e1.printStackTrace(); } return false; } 然后前端jsp 在else alert(data.msg) 56 退出功能 index.jsp 添加shiro的标签库在顶部 <%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro" %> <%--顶部--%> <div data-options="region:\'north\'" style="height:100px; background: #ec4e00; padding: 20px 20px; position: relative;"> <img src="static/images/main_logo.png" alt=""> <div style="position: absolute; right: 50px; top: 30px;"> <img src="./static/images/user.png" style="vertical-align: middle; margin-right: 10px;" > <%--显示当前登录用户名--%> <span style="color: white; font-size: 20px; margin-right: 5px;"><shiro:principal property="username" /> </span> <%--取消认证 跳转到 登录页面 在shiro配置文件当中 配置 /logout = logout --%> <a style="font-size: 18px; color: white;text-decoration: none;" href="${pageContext.request.contextPath}/logout">注销</a> </div> </div> 引用shiro的标签<shiro:principal property="username" /> 在application-shiro.xml的配置中 添加 /logout = logout <!--取消认证 注销功能--> 这个路径请求的时候logout 在前端页面index.jsp中 <%--取消认证 跳转到 登录页面 在shiro配置文件当中 配置 /logout = logout --%> <a style="font-size: 18px; color: white;text-decoration: none;" href="${pageContext.request.contextPath}/logout">注销</a> 57. 注解授权添加 EmpoleeController中添加注解 @RequiresPermissions("employee:index") @RequestMapping("/employee") @RequiresPermissions("employee:index") public String employee(){ return "employee"; } EmpoleeRealm /** 授权 web doGetAuthorizationInfo 什么时候调用 1.发现访问路径对应的方法上面有授权的注解 就会调用doGetAuthorizationInfo授权方法 2.页面中有授权的标签 也会调用doGetAuthorizationInfo授权方法 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { System.out.println("授权调用index---------"); return null; } 1.在配置文件application-shiro.xml当中添加Shiro注解扫描 <!-- 配置为true即使用cglib继承的方式, false为jdk的接口动态代理 控制器没有实现接口 --> <aop:config proxy-target-class="true" ></aop:config> <!-- 使用第三方去扫描shiro的注解 --> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor "> <property name="securityManager" ref="securityManager"></property> </bean> 58. 授权角色添加 在realm中添加授权信息 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { System.out.println("授权调用index---------"); /*获取用户的身份信息*/ Employee employee = (Employee) principals.getPrimaryPrincipal(); /*根据当前用户,查询角色权限*/ List<String> roles = new ArrayList<>(); List<String> permissions = new ArrayList<>(); /*查询角色*/ roles = employeeService.getRolesById(employee.getId()); /*给授权信息*/ SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.addRoles(roles); info.addStringPermissions(permissions); return info; } employeeService /*根据id查询角色*/ List<String> getRolesById(Long id); employeeServicelmpl /*根据id查询角色编号名称*/ @Override public List<String> getRolesById(Long id) { return employeeMapper.getRolesById(id); } EmployeeMapper /*根据id查询用户角色编号*/ List<String> getRolesById(Long id); EmployeeMapper.xml <!--根据用户id查询角色编号--> <select id="getRolesById" resultType="java.lang.String"> SELECT r.rnum FROM employee_role_rel as er LEFT JOIN role as r ON er.rid=r.rid WHERE eid=#{id} </select> 59. 授权权限添加 EmployeeRealm 添加 /*查询权限*/permissions = employeeService.getPermissionsById(employee.getId()); /** 授权 web doGetAuthorizationInfo 什么时候调用 1.发现访问路径对应的方法上面有授权的注解 就会调用doGetAuthorizationInfo授权方法 2.页面中有授权的标签 也会调用doGetAuthorizationInfo授权方法 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { System.out.println("授权调用index---------"); /*获取用户的身份信息*/ Employee employee = (Employee) principals.getPrimaryPrincipal(); /*根据当前用户,查询角色权限*/ List<String> roles = new ArrayList<>(); List<String> permissions = new ArrayList<>(); /*查询角色*/ roles = employeeService.getRolesById(employee.getId()); System.out.println("role==="+roles); /*查询权限*/ permissions = employeeService.getPermissionsById(employee.getId()); /*给授权信息*/ SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.addRoles(roles); info.addStringPermissions(permissions); return info; } employeeService /*根据id查询权限名称*/ List<String> getPermissionsById(Long id); employeeServicelmpl /*根据id查询权限*/ @Override public List<String> getPermissionsById(Long id) { return employeeMapper.getPermissionsById(id); } employeeMapper.xml SQL语句通过DISTINCT 去除重复的值 <!--根据用户id查询权限资源名称--> <select id="getPermissionsById" resultType="java.lang.String"> SELECT DISTINCT p.presource FROM role_permission_rel as rp LEFT JOIN permission as p ON rp.pid = p.pid where rid in (SELECT rid FROM employee_role_rel where eid=#{id}); </select>