一、登录与密码管理
(一)主要任务:
1、主界面与登录实现
2、菜单动态读取实现(重点)
3、密码加密实现
4、修改密码实现
5、管理员重置密码实现
6、员工管理去掉密码修改,确保一个入口,且更新雇员信息,密码变为空的解决方案
- 员工管理去掉密码
7、静态页面传参(实现一个页面显示多种数据)
(二)具体实现
1、主界面与登录实现
-
导入登录界面框架
寻找一款漂亮的后台主界面
将以下内容复制到erp_parent目录下的webapp目录下:
-
实现用户登录功能:
(1)修改前端显示层:
寻找一个漂亮的登录界面框架
将以下内容复制到erp_parent目录下的webapp目录下:
(2)数据访问层修改:
扩展IEmpDao接口,以及其实现EmpDao,添加如下方法:
EmpDao接口:
EmpDao实现类:
(3)业务逻辑层修改:
扩展IEmpBiz接口,及其实现EmpBiz添加如下方法:
IEmpBiz接口:
IEmpBiz实现类:
(4)前端控制层修改:
添加LoginAction类,实现用户登录功能的Action,添加方法checkUser方法,代码如下:
package cn.itcast.erp.action; import java.io.IOException; import java.util.HashMap; import java.util.Map; import javax.servlet.http.HttpServletResponse; import org.apache.struts2.ServletActionContext; import com.alibaba.fastjson.JSON; import com.opensymphony.xwork2.ActionContext; import cn.itcast.erp.biz.IEmpBiz; import cn.itcast.erp.entity.Emp; /** * 实现用户登陆与登出功能 * @author Administrator * */ public class LoginAction { /** 登陆用户名 */ private String username; /** 登陆密码 */ private String pwd; private IEmpBiz empBiz; /** * 登陆验证请求 */ public void checkUser(){ try{ //验证登陆 Emp loginUser = empBiz.findByUsernameAndPwd(username, pwd); if(null == loginUser){ ajaxReturn(false, "用户名或密码不正确"); return; } //保存到session中,表示用户已经登陆了 ActionContext.getContext().getSession().put("loginUser", loginUser); ajaxReturn(true,""); }catch(Exception ex){ ex.printStackTrace(); ajaxReturn(false,"登陆失败"); } } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPwd() { return pwd; } public void setPwd(String pwd) { this.pwd = pwd; } public void setEmpBiz(IEmpBiz empBiz) { this.empBiz = empBiz; } /** * 返回前端操作结果 * @param success * @param message */ public void ajaxReturn(boolean success, String message){ //返回前端的JSON数据 Map<String, Object> rtn = new HashMap<String, Object>(); rtn.put("success",success); rtn.put("message",message); write(JSON.toJSONString(rtn)); } /** * 输出字符串到前端 * @param jsonString */ public void write(String jsonString){ try { //响应对象 HttpServletResponse response = ServletActionContext.getResponse(); //设置编码 response.setContentType("text/html;charset=utf-8"); //输出给页面 response.getWriter().write(jsonString); } catch (IOException e) { e.printStackTrace(); } } }
配置LoginAction交给Spring管理
修改applicationContext.xml文件配置如下:
<!-- 登陆/退出 --> <bean id="loginAction" class="cn.itcast.erp.action.LoginAction" scope="prototype"> <property name="empBiz" ref="empBiz"></property> </bean>
修改struts.xml文件配置如下:
<!-- 登陆/退出 --> <action name="login_*" class="loginAction" method="{1}"></action>
(5)修改登录界面,与LoginAction相匹配:
引入easyui相关css及js文件如下:
<link rel="stylesheet" type="text/css" href="css/login.css"/> <link rel="stylesheet" type="text/css" href="ui/themes/default/easyui.css"> <link rel="stylesheet" type="text/css" href="ui/themes/icon.css"> <script type="text/javascript" src="ui/jquery.min.js"></script> <script type="text/javascript" src="ui/jquery.easyui.min.js"></script> <script type="text/javascript" src="ui/locale/easyui-lang-zh_CN.js"></script> <script type="text/javascript" src="ui/jquery.serializejson.min.js"></script>
修改登录的form表单,内容如下:
为登录按钮绑定事件:
登录按钮绑定的方法(利用AJAX提交表单数据):
-
显示登录用户名:
LoginAction添加showName方法:
修改主界面html显示用户名:
修改主界面显示用户名的JS脚本:
/** * 显示登陆用户名 */ function showName(){ $.ajax({ url:'login_showName', method: 'post', dataType: 'json', success: function(rtn){ if(rtn.success){ //登陆成功的,则显示登陆用户名称 $('#username').html(rtn.message); }else{ //没有登陆的,则跑转到登陆页面 location.href="login.html"; } } }); }
主页面初始化时,调用showName的js方法,修改index.js:
-
退出登录的实现
修改LoginAction,添加loginOut方法:
为安全退出按钮添加绑定事件如下:
//退出登出 $('#loginOut').bind('click',function(){ $.ajax({ url: 'login_loginOut', success:function(rtn){ location.href="login.html"; } }); });
2、菜单动态读取实现(重点)
-
分析菜单结构,构造菜单表
首先观察index.js中的菜单_menus中存储的JSON数据:
从以上的_menus中的JSON数据中可以看出,该表应具备四个基本字段,即:icon、menuid、menuname、url。并且由以上可以看出,每个菜单都有一个上级菜单,此时,我们可以在该表中添加一个字段,即pid(上级菜单ID),这样,当查询一个菜单时,其子菜单都会被查出,就可以构造一个无限层级树形结构的菜单,其表结构最终确定如下:
建表语句
CREATE TABLE MYERPUSER.MENU ( MENUID VARCHAR(30) NOT NULL PRIMARY KEY, MENUNAME VARCHAR(30) NOT NULL, URL VARCHAR(200), ICON VARCHAR(100), PID VARCHAR(30) NOT NULL ) TABLESPACE MYERP; COMMENT ON TABLE MYERPUSER.MENU IS '系统菜单'; COMMENT ON COLUMN MYERPUSER.MENU.MENUID IS '编号'; COMMENT ON COLUMN MYERPUSER.MENU.MENUNAME IS '名称'; COMMENT ON COLUMN MYERPUSER.MENU.URL IS '对应URL'; COMMENT ON COLUMN MYERPUSER.MENU.ICON IS '图标样式'; COMMENT ON COLUMN MYERPUSER.MENU.PID IS '上一级菜单编号';
插入数据语句:
INSERT INTO MENU (MENUID, MENUNAME, URL, ICON, PID) VALUES ('0', '系统菜单', '-', 'icon-sys', '-1'); INSERT INTO MENU (MENUID, MENUNAME, URL, ICON, PID) VALUES ('100', '基础数据', '-', 'icon-sys', '0'); INSERT INTO MENU (MENUID, MENUNAME, URL, ICON, PID) VALUES ('101', '商品类型', 'goodstype.html', 'icon-sys', '100'); INSERT INTO MENU (MENUID, MENUNAME, URL, ICON, PID) VALUES ('102', '商品', 'goods.html', 'icon-sys', '100'); INSERT INTO MENU (MENUID, MENUNAME, URL, ICON, PID) VALUES ('105', '仓库', 'store.html', 'icon-sys', '100'); INSERT INTO MENU (MENUID, MENUNAME, URL, ICON, PID) VALUES ('200', '人事管理', '-', 'icon-sys', '0'); INSERT INTO MENU (MENUID, MENUNAME, URL, ICON, PID) VALUES ('201', '部门', 'dep.html', 'icon-sys', '200'); INSERT INTO MENU (MENUID, MENUNAME, URL, ICON, PID) VALUES ('202', '员工', 'emp.html', 'icon-sys', '200'); COMMIT;
-
后台代码实现:
- 修改 Menu类,修改其成员pid的类型为Menu且名称为menus
> - 配置Menu类的映射文件Menu.hbm.xml,添加自身关联,并且是有序的
- 修改IBaseDao,由于Menu的ID为String,所以我们需要重载get(Long id)的方法
- 修改BaseDao,实现IBaseDao中重载的方法
- 修改IBaseBiz,也是重载get(Long id)的方法
- 修改BaseBiz,实现IBaseBiz中的重载方法
- 修改MenuAction,添加getMenuTree的方法,获取菜单列表
- 修改 Menu类,修改其成员pid的类型为Menu且名称为menus
-
前端代码实现:
访问http://localhost:8080/erp/menu_getMenuTree,获取后台响应过来的菜单的JSON数据:
根据以上获取到的后台数据,修改index.js,使用AJAX动态加载前端数据
3、登录密码加密
- 概述:
我们在前期开发阶段,为了便于测试,密码采用明码存储。一旦程序部署到生产环境上,明友存储密码是非常不安全的,必须要对密码进行加密运算。
加密主要分为两种:可逆运算的和不可逆- 可逆运算:
- 可逆运算的加密是通过一个秘钥,对一串字符进行加密运算,同样可以通过这个秘钥进行 解密运算。
- 不可逆运算:
- 不可逆运算的加密对一串字符进行加密,但是不能还原成原来的字符串(散列)。
- 可逆运算:
- 我们采用的加密就是使用不可逆加密的MD5加密算法。
-
引入pom.xml的shiro的依赖:
<shiro.ver>1.2.3</shiro.ver> 加入shiro的依赖 <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>${shiro.ver}</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>${shiro.ver}</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>${shiro.ver}</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-aspectj</artifactId> <version>${shiro.ver}</version> </dependency> </dependencies>
-
- 后台代码实现
- 为EmpBiz添加Integer类型的hashIterators属性,存放加密时的散列次数
- 修改EmpBiz,重写父类BaseBiz的add方法,因为要在保存时进行密码加密
- 查询时,将用户传入的密码进行加密,在进行与数据库中的密码比对,所以修改findByUsernameAndPwd方法如下:
- 为EmpBiz添加Integer类型的hashIterators属性,存放加密时的散列次数
4、修改密码实现
- 后台代码编写:
- 修改IEmpDao接口中,添加更新用户密码的方法:
- 修改EmpDao类,实现接口中新增的更新用户密码的方法
- 在业务层抽取加密算法如下:
/** * MD5加密 * @param src 原密码 * @param salt 盐 确保相同的密码加密结果不一致 * @return */ private String encrypt(String src, String salt){ Md5Hash md5 = new Md5Hash(src,salt, hashIterations); return md5.toString(); }
- 修改IEmpBiz接口,添加修改密码的方法定义
- 修改EmpBize类,实现接口中定义的修改密码的方法
- 自定义一个异常类,用于业务逻辑出错时,不采用返回值来返回结果,而是用异常抛出错误信息
- 在BaseAction中添加获取当前登录的用户的方法(由于可能经常获取登录用户,故写到BaseAction中)
- 在EmpAction中,添加能接受新旧密码的属性:
- 在EmpAction中添加更新用户密码的方法:
- 自定义一个异常类,用于业务逻辑出错时,不采用返回值来返回结果,而是用异常抛出错误信息
- 修改IEmpDao接口中,添加更新用户密码的方法:
- 前台代码修改:
-
修改密码修改弹窗:
<!--修改密码窗口--> <div id="w" class="easyui-dialog"> <div class="easyui-layout" fit="true"> <div region="center" border="false" style="padding: 10px; background: #fff;"> <table cellpadding=3> <tr> <td>旧密码:</td> <td><input id="txtOldPass" type="password" class="txt01" /></td> </tr> <tr> <td>新密码:</td> <td><input id="txtNewPass" type="password" class="txt01" /></td> </tr> <tr> <td>确认密码:</td> <td><input id="txtRePass" type="password" class="txt01" /></td> </tr> </table> </div> </div> </div>
-
修改index.js中的代码中的openPwd,提交修改的密码时,使用弹窗的按钮事件handler中的方法进行提交:
//修改密码 function openPwd() { $('#w').dialog({ title: '修改密码', width: 300, modal: true, closed: true, height: 192, buttons:[ {text:'保存',iconCls:'icon-save',handler:function(){ var $oldpass = $('#txtOldPass'); var $newpass = $('#txtNewPass'); var $rePass = $('#txtRePass'); if (!$oldpass.val()) { $.messager.alert('提示信息','请输入原密码!','info',function(){ $oldpass.select(); }) return false; } if (!$newpass.val()) { $.messager.alert('提示信息','请输入新密码!','info',function(){ $newpass.select(); }) return false; } if (!$rePass.val()) { $.messager.alert('提示信息','请再一次输入密码!','info',function(){ $rePass.select(); }) return false; } if ($newpass.val() != $rePass.val()) { $.messager.alert('提示信息','两次密码不一至!请重新输入','info',function(){ $rePass.select(); }) return false; } $.ajax({ url:'emp_updatePwd', dataType:'json', type:'post', data: {oldPwd:$oldpass.val(), newPwd:$newpass.val()}, success:function(rtn){ $.messager.alert('提示',rtn.message,'info',function(){ if(rtn.success){ $oldpass.val(''); $newpass.val(''); $rePass.val(''); $('#w').dialog('close'); } }); } }); }}, {text:'取消',iconCls:'icon-cancel',handler:function(){ $('#w').dialog('close'); }} ] }); }
-
5、管理员重置密码实现
-
后台代码实现
- 修改IEmpBiz接口,添加updatePwd_reset(Long uuid,String pwd)方法
- 修改EmpBiz类,实现IEmpBiz接口中新增的重置密码的方法
- 修改Action层,增加updatePwd_reset方法,供前台调用
- 修改IEmpBiz接口,添加updatePwd_reset(Long uuid,String pwd)方法
-
前端代码实现
-
新增pwd.html,代码如下:
-
新增pwd.js,代码如下:
$(function(){ $('#grid').datagrid({ title: '员工信息列表', url:'emp_listByPage', columns:[[ {checkbox:true}, {field:'uuid',title:'编号',width:100}, {field:'username',title:'系统登陆名称',width:100}, {field:'name',title:'真实姓名',width:100}, {field:'gender',title:'性别',width:100,formatter:function(gender){ switch(gender){ case 0: return '女'; case 1: return '男'; default: return '未知'; } }}, {field:'email',title:'电子邮箱地址',width:100}, {field:'tele',title:'联系电话',width:100}, {field:'address',title:'联系地址',width:100}, {field:'birthday',title:'出生年月日',width:100,formatter:function(birthday){ if(birthday){ return new Date(birthday).Format("yyyy-MM-dd"); } return ""; }}, {field:'dep',title:'部门',width:100,formatter:function(dep){ if(dep){ return dep.name; } return ""; }}, {field:'-',title:'操作',width:200,formatter:function(value,row,index){ var operation = '<a href="javascript:void(0)" onclick="updatePwd_reset(' + row.uuid + ')">重置密码</a> '; return operation; }} ]], singleSelect: true, pagination: true, rownumbers: true }); $('#btnSave').bind('click',function(){ if($('#formPwdReset').form('validate') == false){ return; } var datas = $('#formPwdReset').serializeJSON(); $.ajax({ url: 'emp_updatePwd_reset', dataType:'json', data: datas, type: 'post', success: function(rtn){ if(rtn.success){ $.messager.alert("提示信息", "重置密码成功!", 'info', function(){ //关闭窗口 $('#dlgPwdReset').window('close'); //刷新列表 $('#grid').datagrid('reload'); }); }else{ $.messager.alert("提示信息", "重置密码失败!<br>" + rtn.message, 'info'); } } }); }); $('#editDlg').dialog({ title:"重置员工密码", width:260, height:120, iconCls:'icon-save', modal:true, closed:true, buttons:[ { text:'保存', iconCls: 'icon-save', handler:function(){ var datas = $('#editForm').serializeJSON(); $.ajax({ url: 'emp_updatePwd_reset', dataType:'json', data: datas, type: 'post', success: function(rtn){ $.messager.alert("提示", rtn.message, 'info', function(){ if(rtn.success){ //关闭窗口 $('#editDlg').dialog('close'); //刷新列表 $('#grid').datagrid('reload'); }else{ $.messager.alert("提示", "重置密码失败!<br>" + rtn.message, 'info'); } }); } }); } } ] }); }); function updatePwd_reset(uuid){ //清除表单内容,防止数据混乱 $('#editForm').form('load',{id:uuid,newPwd:""}); //弹出窗口 $('#editDlg').dialog('open'); }
-
6、员工管理去掉密码修改,确保一个入口,且更新雇员信息,密码变为空的解决方案
- 员工管理去掉密码
- 更新雇员信息,密码变为空的解决方案
- 产生的原因
当我们修改用户信息时,会调用save方法,由于修改用户信息时,并未涉及到密码,所以前台提交的数据,到后台封装为用户对象后,用户对象中的密码为空值,再加上调用save方法,所以密码会变为空
- 解决方案:
-
添加触发器,当修改每一条数据时,在保存时,如果某个字段没有值,将被设为之前的值(该方法比较忌讳的,因为是遍历每一行记录,所以会为表上锁,效率降低很多)
CREATE OR REPLACE TRIGGER TRI_EMP_UPDATE_PWD BEFORE UPDATE OF PWD ON EMP FOR EACH ROW DECLARE --声明变量 BEGIN --如果修改的密码为NULL ,让修改的密码为原密码 --伪记录 :OLD 修改之前的记录 :NEW 修改之后的记录 IF :NEW.PWD IS NULL THEN :NEW.PWD := :OLD.PWD; END IF; END;
-
先让该类持久化,之后再为除了密码字段的该对象的其他字段,赋值新的值
@Override public void update(Emp emp){ //查出要更新的对象,让其进入持久化状态 Emp dbEmp = this.empDao.get(emp.getUuid()); if(null == dbEmp){ throw new ERPException("没有找到相应的员工!"); } //修改真实姓名 dbEmp.setName(emp.getName()); //修改联系电话 dbEmp.setTele(emp.getTele()); //修改联系地址 dbEmp.setAddress(emp.getAddress()); //修改出生年月日 dbEmp.setBirthday(emp.getBirthday()); //修改邮箱地址 dbEmp.setEmail(emp.getEmail()); //修改所属部门 dbEmp.setDep(emp.getDep()); }
-
在Emp.hbm.xml中,设置pwd字段的update=false,之后在进行更新,将在不再更新pwd字段(推荐做法)
-
- 产生的原因
7、新增员工设置初始密码
- 需求:当我们添加一个雇员时,为其设置初始密码,为用户的登录名
- 后台代码实现
- 修改EmpBiz层即可,在添加Emp雇员时,即add方法,先将用户名加密,作为密码传入进去
- 修改EmpBiz层即可,在添加Emp雇员时,即add方法,先将用户名加密,作为密码传入进去
7、静态页面传参,将多种类似的数据,通过一个页面显示,通过静态页面传参,将相似的同一个表格的不同代表意义的数据分类管理
-
需求分析:当两种数据,其对应的数据库中的表完全一样时,我们在增加一列Type,可以根据他们的Type,进行分类管理。
-
原理:把相似的信息放入同一个表,可以极大的节省开发时间,降低维护成本,这两个信息对应的实体层、数据访问层、业务层、页面都用同一套代码,只是通过参数进行区分。
-
前端页面实现(给supplier.html页面传参数type):
-
引入静态页面传参,要引入的外部依赖JS文件:
-
request.js的代码如下:
在这里插入代码片
-
修改SupplierDao类,加上type条件,以区分查询的是供应商还是客户
-
修改crud.js文件,加入全局变量listParam,根据type的值进入赋值
-
删除原有type列或行
-
修改保存功能(增加saveParam变量,记录保存url提交的type参数)
- 修改btnSave方法,提交的url中加入saveParam
- 修改菜单url(更新menu表中对应菜单的url,把供应商的url后面加上?type=1;把客户的url后面加上?type=2)