1. 项目的开发说明和准备
-
项目github仓库:https://github.com/Xiemaoshu/EMProject.git
-
这个项目的案例的核心模块只有两大块,但是这两大块会涉及到许多验证以及业务相关的操作处理部分.在整个项目中关注的点有如下几点
- 了解整个项目的开发的流程
- 所有的项目核心就是crud,单表,一对多,多对多;
- 感受如果做一个全栈工程师.
- 本次项目不使用流行框架完成,观察不使用框架的问题所在.
-
核心功能
-
做一个公司内部的人事管理系统,主要是负责雇员的操作操作管理,也就是说所有的操作都围绕着雇员的crud操作,但是这一过程之中需要注意一下几点:
- 公司的工资需要进行严格的等级控制一定要注意如果要新增员工,那么新员工必须在指定的等级范围之中.
- 公司的所有员工都要求有部门,但是每一个部门都有人员上限(上限有管理员定义好的)那么就意味着在进行员工添加的时候需要考虑到部门上限的控制,当员工离职的时候需要考虑到部门人数的减少.
- 考虑到员工的信息安全,必须有人是管理员负责员工的添加以及修改等操作,但是需要将每一次的修改记录进行保存.
- 在进行部门列表的时候要求可以在本页面上弹出一个新的层来进行员工的信息列出,同时此列表可以进行分页控制(无刷新分页)
- 整个的系统中不允许出现删除操作
-
本系统中还有一个关键的问题,权限认证问题,权限认证如果真要操作,一定在控制层和业务层上进行限制.
-
如果从MVC标准开发来讲,整个的开发之中任何一个JSP页面都不允许直接链接跳转到另一个JSP页面,必须使用Servlet跳转,必须使用MVC设计模式完成
-
核心技术
-
MVC+JQuery+Bootstrap
1.2 数据库设计
- 用户表
- 在整个的代码之中,密码一定要进行加盐处理,所以本次在进行测试数据准备的时候也准备好了salt.
- salt:mldnjava,Base64:“bWxkbmphdmE=”,格式:密码 password({salt})
- 在用户表中有一个超级管理员标记,一个系统中有且只有一个超级管理员标记,超级管理员是绝不允许删除的,而后所有的用户要想进行操作控制,那么就必须有角色
- 超级管理员标记的项有两个内容:
- 普通用户:sflag=0;
- 超级管理员:sflag=1;
- 角色表
- 一个用户拥有多个角色信息,而且一个角色会有对应有多个权限
- 在"用户-角色"关系表之中,其主导作用的是用户表,在角色表中有一个最为重要的字段:flag.再将在整个程序中进行角色验证关键因素所在.
- 权限关系:一个角色对应有多个权限信息
- 在权限表中拥有一个标记信息,这个信息于角色标记作用如出一辙,它里面保存也是程序之中需要进行判断的权限名称标记.
数据字典表
- 部门表
- 在部门之中有一个最关键的部分称为最大人数,在进行雇员信息添加的时候,一定要保证当前的人数不超过最大人数,还有一个当前人数,表示部门已经存在的人数,表示当雇员增加的时候或离职的时候需要进行此类信息的更改.
- 职位等级表:level
-
职位等级一定要和工资对等,也就是说现在增加了一个新雇员,但是设置的工资内容超过了等级范围,表示这是一个错误的数据,将不允许雇员的添加.
-
在进行设计过程之中也为项目进行了简化,例如:要设计部门的经理信息,那么必须要求经历达到了某个等级之后才能够进行安排,而且一个部门可能有一个经理,还有两个副经理.
1.3雇员信息表
- 用户登录之后具备有雇员权限的用户应该具备有雇员信息的crud处理(不包含删除),在进行雇员信息添加的时候需要添加以下内容,
- 其中用户ID指的就是添加该雇员信息的管理员,那么也就是说人事用户.其中雇员的雇佣日期需要使用当前日期时间生成.
- 而对于雇员的照片,需要保存两份,一个是原始图片,另一份是压缩图片,现实的时候显示压缩图片
- 雇员信息日志表
- 每一个雇员的创建,修改,离职等操作都要求在固原日志信息表上进行列出.
-
这个备注要求记录好雇员信息变更时的相关意见.所有的备注信息只允许出现查询和增加两个功能.
-
这几张表中都是最基础的业务逻辑操作,那么关键是这些标记字段的设计
-
完整数据表关系图,使用PowerDesigner数据库建模工具软件
1.4页面组成分析
- 最理想的状态是只写后台业务代码,前台的开发都有美工完成,但是在中国软件开发公司中,一般都由程序员完成.
- 整个界面的功能由如下几个部分组成
- 登录界面,这里面需要使用ajax进行页面处理
- 主页面,包含所有页面信息
- 雇员列表
- 部门列表
- 雇员操作界面
WEBROOT----------web项目的发布目录
js:-----------------------保存所有的\*.js文件
-----|page
------------|back
--------------------|emp
---------------------------|emp_add.js-------专门为emp_add.jap页面服务
----|login.js ----------------这个为跟路径下的login.js服务
login.jsp---------------扶着登录界面显示
|pages------------------保存所有的页面
----|index.jsp--------保存所有首页信息
----|back--------------保存所有需要进行过滤检测的程序
------------|member--------后台用户系统
-------------|dept------------部门模块的文件
-------------|emp-------------雇员模块信息
-------------------|emp_add.jsp
2. 用户登录模块开发
2.1模块开发要求
-
整个项目之设计了一个单用户的登录用户操作,所以所有的操作都必须要求用户登录之后才能够操作
-
用户登录之后要求去除所有的角色信息和权限信息,这些信息是用来控制前台链接使用的
- 为了方便进行角色或权限数据的查找,那么建议讲这些权限或角色的信息保存在Map集合里面
-
用户登录时考虑到安全性需要使用验证码
- 验证码所使用到的开发包:kaptcha
-
进入到首页之后需要进行页面链接的动态判断
-
使用的数据库
- 用户要求输入用户名和密码进行登录
- 登录成功之后需要取出用户的角色或者是权限
- 进入到首页之后需要根据角色或者是权限动态判断那些链接需要生成,所有的链接地址徐奥由开发者自己设置好相应路径,其基本原则:所有的jsp页面必须提交到servlet之后才可以跳转到jsp页面
- 考虑到浏览器的屏蔽问题,不建议使用弹出框进行提示信息,建议使用倒计时的方式完成
2.2基础登录实现
- 项目初始代码:https://github.com/Xiemaoshu/EMProject.git
- 基础登录就是指不关心验证码,不关心权限或是角色数据,只是进行用户名于密码的验证处理,
- 建立Member表的vo类:mao.shu.emp.vo.Member
package mao.shu.emp.vo;
import java.io.Serializable;
public class Member implements Serializable {
private String mid;
private String password;
private String name;
private int sflag;
public String getMid() {
return mid;
}
public void setMid(String mid) {
this.mid = mid;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getSflag() {
return sflag;
}
public void setSflag(int sflag) {
this.sflag = sflag;
}
}
- 对于vo类需要实现Serializable接口,可以序列化对象.
- 建立IMemberDAO接口,这个接口直接继承自IDA接口
package mao.shu.em.dao;
import mao.shu.em.vo.Member;
import mao.shu.util.IDAO;
public interface IMemberDAO extends IDAO<String, Member> {
}
- 建立MemberDAO子类,这个子类只需要复写findById()方法
public class MemberDAO extends AbstractDAO implements IMemberDAO {
@Override
public Member findById(String id) throws SQLException {
String sql="SELECT mid,password,name,sflag FROM member WHERE mid=?";
super.pstmt = super.conn.prepareStatement(sql);
ResultSet rs = super.pstmt.executeQuery();
Member vo = null;
if(rs.next()){
vo = new Member();
vo.setMid(rs.getString(1));
vo.setPassword(rs.getString(2));
vo.setName(rs.getString(3));
vo.setSflag(rs.getInt(4));
}
return vo;
}
}
- 建立一个业务的操作,考虑到这是一个后台项目,为了日后能够有功能扩充,定义的接口都属于后台业务接口,所以建议将其保存在mao.sji.em.service.back包下,而建立的业务结构名称为:IMemberServiceBack
package mao.shu.em.service.back;
import mao.shu.em.vo.Member;
import java.util.Map;
public interface IMemberServiceBack {
/**
* 实现用户登录的方法,该方法主要完成一下功能<br>
* 1. 实现登录检测,调用 IMemberDAP.findById()方法<br>
* 2. 获取用户的所有角色信息<br>
* 3. 获取所有的用户权限信息<br>
* @param vo 包含一个试图登录的用户的基本信息,输入的用户名和密码
* @return 该方法将所有的信息以Map集合形式返回,map集合中会拥有以下内容
* 1. key = flag value= true | false; 标志着用户登录成功或失败的标记<br>
* 2. key = sflag value = 0 | 1 判断当前用户是否为超级管理员
* 3. key = name value = 用户的真是姓名
* 4. key = allRoles value = set集合 保存用户所有的角色数据
* 5. key = allActions value =set集合 保存用户所有的权限信息
* @throws Exception
*/
public Map<String,Object> login(Member vo)throws Exception;
}
- 随后不要直接建立业务层子类,最好在业务层接口和子类之间设置一个抽象类:AbstractService
- 但是这个类现阶段什么都不需要处理
package mao.shu.em.service.abs;
public class AbstractService {
}
- 建立MemberServiceBackImpl子类
package mao.shu.em.service.back.impl;
import mao.shu.em.dao.IMemberDAO;
import mao.shu.em.service.back.IMemberServiceBack;
import mao.shu.em.vo.Member;
import mao.shu.util.factory.DAOFactory;
import java.util.HashMap;
import java.util.Map;
public class MemberServiceBackImpl implements IMemberServiceBack {
@Override
public Map<String, Object> login(Member vo) throws Exception {
Map<String,Object> resultMap = new HashMap<String,Object>();
IMemberDAO memberDAO = DAOFactory.getInstance(IMemberDAO.class);
Member ckVO = memberDAO.findById(vo.getMid());
if(ckVO.getPassword().equals(vo.getPassword())){
resultMap.put("flag",true);//保存用户登录结果
resultMap.put("name",ckVO.getName());//保存哟凝固的真实姓名
resultMap.put("sflag",ckVO.getSflag());//保存用户的超级管理员标记
}else{
resultMap.put("flag",false);
}
return null;
}
}
- 建立MemberLoginServlet,这里面要包含有两个功能,登录检测和注销功能
- 修改Message.properties 文件增加相应的登陆成功提示信息
login.success.msg=用户登录成功!
login.failure.msg=用户登录失败(请确认用户名或密码正确)!
- 修改pages.properties文件,增加登录页映射操作
login.page=/login.jsp
index.page=/pages/index.jsp
- 为了方便进行MD5加密处理,编写一个专门负责加密处理得类 :EncryptUtil
package mao.shu.util;
public class EncryptUtil {
public static final String salt = "bWxkbmphdmE=";
public static String getPassword(String password){
return new MD5Code().getMD5ofStr(password + "({" + salt + "})");
}
}
- 在DispatcherServlet类中添加以下的方法
public HttpSession getSession(){
return this.request.getSession();
}
public void setSessionAttribute(String name,String value){
this.request.getSession().setAttribute(name,value);
}
public HttpServletRequest getRequest(){
return this.request;
}
public void setRequestAttribute(String name,String value){
this.request.setAttribute(name,value);
}
- 完善MemberLoginServlet类中登录方法
public String login(){
//进行加密处理
this.member.setPassword(EncryptUtil.getPassword(this.member.getPassword()));
IMemberServiceBack memberServiceBack = ServiceFactory.getInstance(MemberServiceBackImpl.class);
try {
Map<String, Object> loginResult = memberServiceBack.login(this.member);
boolean loginFlag = (boolean) loginResult.get("flag");
if(loginFlag){
super.setUrlAndMsg("index.page","login.success.msg");
super.setSessionAttribute("name",loginResult.get("name"));
super.setSessionAttribute("sflag",loginResult.get("sflag"));
}else{
super.setUrlAndMsg("login.page","login.failure.msg");
}
}catch (Exception e){
e.printStackTrace();
}
return "forward.page";
}
- 测试登录效果
3. 登录控制加强
-
首先修改页面,将当前用户的真实姓名,进行显示
- 修改两个页面:i
- include_title_head.jsp,
- include_menu_item.jsp
-
修改"include_title_head.jsp"追加注销链接
<%
String logOutUrl = "MemberLoginServlet/logout";
%>
- 在MemberLoginServlet了之中对于注销的控制,只需要让session失效即可,之后应该跳转到登录页
- 在Message.properties文件中追加注销信息
logout.msg=用户注销成功!
- 完善MemberLoginServlet中的logOut()方法
public String logout(){
super.getSession().invalidate();//使session失效
super.setUrlAndMsg("login.page","logout.msg");
return "forward.page";
}
- 由于整个后台的资源需要登录后才可以使用,name必须保证是已经等了故宫的用户才可以访问,因此建立增加过滤器.
- 添加编码过滤器
package mao.shu.util.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@WebFilter("/pages/*")
public class EncodingFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
servletRequest.setCharacterEncoding("UTF-8");
servletResponse.setCharacterEncoding("UTF-8");
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
}
- 登录成功之后MemberLoginServlet类中一定要将mid添加到session中.
- 修改MemberLoginServlet中的login()方法
super.setSessionAttribute("mid",this.member.getMid());
- 在Message.properties中添加一个未登录的信息
unlogin.msg=您还未登录,请登录后访问
- 添加loginFilter过滤器
package mao.shu.em.filter;
import mao.shu.util.ResourceUtils;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
/**
* @author Xiemaoshu
* @date : 2019年2月5日 21:05:49
* 该类主要用于对/pages/下的所有页面,做一个用户是否登录过滤,,如果用户未登录,则跳转到login.jsp页面中
*
*/
@WebFilter("/pages/*")
public class loginFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpSession session = request.getSession();
if(session.getAttribute("mid")!=null){
filterChain.doFilter(servletRequest,servletResponse);
}else{
String msg = ResourceUtils.get("Messages","unlogin.msg");
String loginPage = ResourceUtils.get("Pages","login.page");
String forwardPage = ResourceUtils.get("Pages","forward.page");
servletRequest.setAttribute("url",loginPage);
servletRequest.setAttribute("msg",msg);
servletRequest.getRequestDispatcher(forwardPage).forward(servletRequest,servletResponse);
}
}
@Override
public void destroy() {
}
}
- 测试效果
- 服务器端检测
- 正常来讲必须使用login.jsp表单进行提交之后才可以登录,如果有破坏意义的用户,那么就希望绕过表单,直接将参数交给MemberloginServlet/login处理
- 所以在Validations.properties文件中添加规则操作
MemberLoginServlet.login.rules = member.mid:string|member.password|string
- 随后还需要在Pages.properties中添加,一个执行MemberLoginServlet后的服务器端检测失败调换页面
#执行MemberLoginServlet的login()方法出现错误时,跳转的页面
MemberLoginServlet.login.error.page=/pages/index.jsp
- 但有时候如果没有写 MemberLoginServlet.login.error.page 这个页面就可能会出现,错误,为了防止错误的放生,可以修改 DispatcherServlet 中的 getPageValue()方法,如果没有设置跳转页面,则统一跳转到 error.page页面中
/**
* 取得Pages.properties文件中指定的key对应的value内容
*
* @param pageKey
* 要读取的资源文件的key信息
* @return
*/
public String getPageValue(String pageKey) {
try {
return this.pageResource.getString(pageKey);
}catch(Exception e){
}
return this.pageResource.getString("error.page");
}
- 可以修改 error.jsp页面,显示出错误的提示信息
<div class="form-bottom" style="background: white;">
<span
class="h5">程序出错了,请返回首页,与管理员联系!
改程序出现了如下的错误:
<ul>
<e:forEach items="${errors}" var="a">
<li>${a.key}:${a.value}</li>
</e:forEach>
</ul>
</span>
</div>
- 既然这个项目代码最终要放到公网上,那么就必须考虑任何安全漏洞
角色与权限
- 实现了基础的登录控制之后下面就需要针对角色和权限进行取出.在取出权限或角色的时候,所取出的是他的标记(flag)的内容.
- 闯进Role 和Action 的vo类
package mao.shu.em.vo;
import java.io.Serializable;
public class Role implements Serializable {
private Integer rid;
private String title;
private String flag;
public Integer getRid() {
return rid;
}
public void setRid(Integer rid) {
this.rid = rid;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getFlag() {
return flag;
}
public void setFlag(String flag) {
this.flag = flag;
}
}
package mao.shu.em.vo;
import java.io.Serializable;
public class Action implements Serializable {
private Integer actid;
private String title;
private String flag;
public Integer getActid() {
return actid;
}
public void setActid(Integer actid) {
this.actid = actid;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getFlag() {
return flag;
}
public void setFlag(String flag) {
this.flag = flag;
}
}
- 定义IRoleDAO于IActionDAO
public interface IRoleDAO extends IDAO<Integer, Role> {
/**
* 该方法根据一个用户id查找该用户的所有角色信息
* @return
* @throws SQLException
*/
public Set<String> findAllByMember(String mid)throws SQLException;
}
public interface IActionDAO extends IDAO<Integer, Action> {
/**
* 该方法根据一个用户id取出该用户的所哟权限信息
* @return
* @throws SQLException
*/
public Set<String> findAllByMember(String mid)throws SQLException;
}
- 实现 RoleDAOImpl 和ActionDAOImpl
public class RoleDAOImpl extends AbstractDAO implements IRoleDAO {
@Override
public Set<String> findAllByMember(String mid) throws SQLException {
String sql = "SELECT flag FROM role WHERE rid IN(" +
"SELECT rid FROM member_role WHERE mid=?)";
super.pstmt = super.conn.prepareStatement(sql);
super.pstmt.setString(1,mid);
ResultSet rs = super.pstmt.executeQuery();
Set<String> rids = new HashSet<String>();
while(rs.next()){
rids.add(rs.getString(1));
}
return rids;
}
}
public class ActionDAOImpl extends AbstractDAO implements IActionDAO {
@Override
public Set<String> findAllByMember(String mid) throws SQLException {
String sql = "select flag from action where actid in( " +
" select actid from role_action where rid in( " +
" select rid from member_role where mid=? " +
" ) " +
" ) ";
super.pstmt = super.conn.prepareStatement(sql);
super.pstmt.setString(1,mid);
ResultSet rs = super.pstmt.executeQuery();
Set<String> action_flags = new HashSet<String>();
while(rs.next()){
action_flags.add(rs.getString(1));
}
return action_flags;
}
}
- 修改MemberServletBack,在用户登录成功之后为将用户的权限flag和角色flag的Set集合保存到登录方法返回的 Map集合中.
@Override
public Map<String, Object> login(Member vo) throws Exception {
Map<String,Object> resultMap = new HashMap<String,Object>();
IMemberDAO memberDAO = DAOFactory.getInstance(MemberDAO.class);
Member ckVO = memberDAO.findById(vo.getMid());
if(ckVO!= null) {
if (ckVO.getPassword().equals(vo.getPassword())) {
IRoleDAO roleDAO = DAOFactory.getInstance(RoleDAOImpl.class);
IActionDAO actionDAO = DAOFactory.getInstance(ActionDAOImpl.class);
resultMap.put("flag", true);//保存用户登录结果
resultMap.put("name", ckVO.getName());//保存用户的真实姓名
resultMap.put("sflag", ckVO.getSflag());//保存用户的超级管理员标记
resultMap.put("allRoles",roleDAO.findAllByMember(vo.getMid()));
resultMap.put("allActions",actionDAO.findAllByMember(vo.getMid()));
} else {
resultMap.put("flag", false);
}
}else{
resultMap.put("flag", false);
}
return resultMap;
}
- 这些数据在登录后的页面之中都需要使用到,所以应该将这些数据添加到添加到Session对象之中
- 修改MemberLoginServlet
super.setSessionAttribute("allRoles",loginResult.get("allRoles"));
super.setSessionAttribute("allActions",loginResult.get("allActions"));
- 随后需要处理的就是菜单的显示处理,因为EL表达式无法使用set.contains()的方法判断Set集合中的数据,所以就必须使用jstl开发包的另一组标签:函数标签
- 将fn.tld的标签文件拷贝到WEB-INF中(该标签文件在jstl的开发包之中)
- 配置web.xml文件进行标签的引入声明
- 修改web.xml文件
<taglib>
<taglib-uri>http://www.mao.su/fn</taglib-uri>
<taglib-location>/WEB-INF/fn.tld</taglib-location>
</taglib>
- 在include_static_head.jsp页面中进行标签的定义:
<%@ taglib prefix="fn" uri="http://www.mao.su/fn" %>
- 修改"include_menu_iterm.jsp页面进行判断角色和权限
<c:if test="${fn:contains(allRoles,'member' )}">
<c:if test="${fn:contains(allRoles,'dept' )}">
<c:if test="${fn:contains(allRoles,'emp' )}">
- 现在不同的用户就会有不同的菜单出现,
验证码检测
- 对于登录的验证码的检测一定要发那个在两个位置上完成
- 客户端检测
- 服务端检测处理
- 如果要进行客户端的验证码处理,那么需要编写一个专门的检测工具类,登录页面使用的验证码生成工具为 KaptchaServlet ,在web.xml文件中对保存到session属性名称中的配置如下
<init-param> <!-- 设置验证码保存到session中的属性名称 -->
<param-name>kaptcha.session.key</param-name>
<param-value>rand</param-value>
</init-param>
- 编写CodeCheckServlet.java程序类,改程序类为一个可重用的代码,所以可以定义在util包下
package mao.shu.util.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/CodeCheckServlet")
public class CodeCheckServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//得到用户表单提交的验证码
String input_code = req.getParameter("code");
//得到KaptchaServlet 生成的验证码
String servlet_code = (String) req.getSession().getAttribute("rand");
if(input_code != null) {
resp.getWriter().print(input_code.equalsIgnoreCase(servlet_code));
}else{
resp.getWriter().print("false");
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req,resp);
}
}
- 在login.js文件中,提供有ajax异步处理的验证方法
"code" : {
required : true ,
remote : {
url : "CodeCheckServlet", // 后台处理程序
type : "post", // 数据发送方式
dataType : "html", // 接受数据格式
data : {
// 要传递的数据
code : function() {
return $("#code").val();
}
},
dataFilter : function(data, type) {
if (data.trim() == "true") {
return true;
} else {
return false;
}
}
}
}
}
});
- 随后需要实现服务器端的验证处理,所以这个时候需要扩充验证工具类,现在假设验证码的检测标记为:rand
- 修改Validations.properties文件,追加验证码检测处理
MemberLoginServlet.login.rules = member.mid:string|member.password:string|code|rand
- 在Message.properties文件中添加一个验证码提示信息,
validation.rand.msg=验证码输入错误
- 后端的验证由Validation.java类完成,因此要修改Validation.java文件,防止用户未填写登录表单,而直接访问index.jsp或者其他需要登录后才可以访问的页面
default : {
//其它类型,表示验证的是验证码.
if(!Validation.validateEmpty(val)){
errors.put(temp[0],servlet.getMessageValue("validation.rand.msg"));
}else{
//如果此时有验证码
//得到Servlet生成的验证码
String rand = (String) servlet.getSession().getAttribute("rand");
//和用户提交的验证码内容进行判断
if(rand==null){
errors.put(temp[0],servlet.getMessageValue("validation.rand.msg"));
}else if(!rand.equalsIgnoreCase(val)){
errors.put(temp[0],servlet.getMessageValue("validation.rand.msg"));
}
}
}
- 这种编写方式最大的好处在于:主控制层MemberServletLogin.java就不需要在进行数据的验证了,所有的验证都交由Validation.java类完成,这样可以做到代码分离,写出简洁的代码.
- 如果此时直接访问 http://localhost:8080/EMProject/MemberLoginServlet/login 会出现以下提示
- 但是此时还会发现一个严重的错误,当用户在输入验证码表单的时候,如果用户输入完正确的验证码,但是还未提交,又点击了验证码图片切换了验证码,此时就会出现一下的错误
-
这是由于用户之前的表单验证结果,还没有在前台页面及时刷新导致的,这使得前台的表单验证处于正确状态,但是用户提交之后,在后台验证出错.
-
为了解决这个问题,可以使用以下的解决思路
- 当用户点击验证码图片切换验证码之后,应该清空验证码输入框的内容,并且恢复表单之前的样式.
-
修改login.js文件,
$("#imageCode").on("click",function(){
$("#imageCode").attr("src","captcha.jpg?p=" + Math.random());
vld.resetForm();//重置表单
$("#code").val("");//清空验证码表单内容
$("#codeDiv").attr("class","form-group")
}) ;
- 修改之后的效果
- 很多的开发问题,一定要多思考,多使用才可以发现
雇员管理模块
开发任务
- 雇员管理模块属于内部内部系统模块,他与登录不同,登录在进行操作的时候,每一个用户都可以进行操作,而雇员管理需要进行权限控制
- 对于权限的控制需要在控制层和业务层两个位置完成
- 控制层的检测可以通过session完成.
- 而业务层需要进行数据库的查询检测
- 雇员入职
- 首先一定要通过EmpServlet/addPre页面进行数据的读取:读取所有的部门数据以及所有的工资等级数据.
- 部门追加管的时候都有人数限制,所以出现的部门都应该是有名额的部门
- 员工等级的出现的主要目的是为了基本工资的合法性检测:
- 前端使用ajax验证,而后端通过数据层的检测验证.
-
对于员工的列表操作,要考虑到分页的处理情况,以及模糊查询的处理问题.
- 在进行员工编辑的时候牵扯到数据回填问题.
-
所有雇员的增加,修改都需要在一个雇员日志表中记录
- 员工的相关信息说明要保存在雇员日志记录表里面
- 每一次修改的时候这个对于雇员日志都属于新的内容,都要求重新保存
雇员增加—业务层
- 雇员增加操作直接就是针对于emp表的insert处理,在整个的处理之中:需要注意以下几点
- 雇员的工资必须符合于所处的职位级别
- 雇员要分配的部门必须有空位
- 部门人数一定要进行增加;
- 建立Emp.java的vo类
package mao.shu.em.vo;
import java.io.Serializable;
import java.util.*;
public class emp implements Serializable {
private Integer empno;
private Integer deptno;
private String mid;
private Integer lid;
private String ename;
private String job;
private Double sal;
private Double comm;
private Date hiredate;
private String photo;
private Integer flag;
//getter和setter方法
}
- . 建立IEmpDAO接口
package mao.shu.em.dao;
import mao.shu.em.vo.Emp;
import mao.shu.util.IDAO;
public interface IEmpDAO extends IDAO<Integer, Emp> {
}
- 建立EmpDAOImpl子类,只需要实现一个doCreate()方法即可
public class EmpDAOImpl extends AbstractDAO implements IEmpDAO {
@Override
public boolean doCreate(Emp vo) throws SQLException {
String sql = "INSERT INTO emp(deptno,mid,lid,ename,job,sal,comm,hiredate,photo,flag)VALUES(?,?,?,?,?,?,?,?,?,?)";
super.pstmt = super.conn.prepareStatement(sql);
super.pstmt.setInt(1,vo.getDeptno());
super.pstmt.setString(2,vo.getMid());
super.pstmt.setInt(3,vo.getLid());
super.pstmt.setString(4,vo.getEname());
super.pstmt.setString(5,vo.getJob());
super.pstmt.setDouble(6,vo.getSal());
super.pstmt.setDouble(7,vo.getComm());
super.pstmt.setDate(8,new java.sql.Date(vo.getHiredate().getTime()));
super.pstmt.setString(9,vo.getPhoto());
super.pstmt.setInt(10,vo.getFlag());
return super.pstmt.executeUpdate()> 0;
}
}
- 建立Level.java的vo类
package mao.shu.em.vo;
import java.io.Serializable;
public class Level implements Serializable {
private Integer lid;
private Double losal;
private Double hisal;
private String title;
private String flag;
//getter 和 setter 方法
}
- 建立ILevelDAO接口,目的是为了根据指定的级别编号查询出工资范围,以方便进行数据验证使用
package mao.shu.em.dao;
import mao.shu.em.vo.Level;
import mao.shu.util.IDAO;
public interface ILevelDAO extends IDAO<Integer, Level> {
}
- 建立LevleDAOImpl的子类.只需要复写findById()方法和findAll()方法即可
public class LevelDAOImpl extends AbstractDAO implements ILevelDAO {
@Override
public Level findById(Integer id) throws SQLException {
String sql = "SELECT lid,losal,hisal,title,flag FROM level WHERE lid=?";
super.pstmt = super.conn.prepareStatement(sql);
super.pstmt.setInt(1,id);
Level vo = null;
ResultSet rs = super.pstmt.executeQuery();
if(rs.next()){
vo = new Level();
vo.setLid(rs.getInt(1));
vo.setLosal(rs.getDouble(2));
vo.setHisal(rs.getDouble(3));
vo.setTitle(rs.getString(4));
vo.setFlag(rs.getString(5));
}
return vo;
}
@Override
public List<Level> findAll() throws SQLException {
String sql = "SELECT lid,losal,hisal,title,flag FROM level";
super.pstmt = super.conn.prepareStatement(sql);
List<Level> allLevels = new ArrayList<Level>();
ResultSet resultSet = super.pstmt.executeQuery();
while(resultSet.next()){
Level vo = new Level();
vo.setLid(resultSet.getInt(1));
vo.setLosal(resultSet.getDouble(2));
vo.setHisal(resultSet.getDouble(3));
vo.setTitle(resultSet.getString(4));
vo.setFlag(resultSet.getString(5));
}
return allLevels;
}
}
- 建立IDeptDAO接口,需要确定人数
- 建立Dept.java的vo类
package mao.shu.em.vo;
import java.io.Serializable;
public class Dept implements Serializable {
private Integer deptno;
private String dname;
private Integer maxnum;
private Integer currnum;
//getter 和setter 方法
}
- 建立IDeptDAO接口,但是在这个接口里面需要扩充两个方法
- 列出所有有空余人数的部门数据,方便在进
- 进行部门当前人数的更新处理操作
package mao.shu.em.dao;
import mao.shu.em.vo.Dept;
import mao.shu.util.IDAO;
import java.sql.SQLException;
import java.util.List;
public interface IDeptDAO extends IDAO<Integer, Dept> {
/**
* 查询出所有还未满员的部门
* @return 所有未满员的部门数据
* @throws SQLException
*/
public List<Dept> findUnders()throws SQLException;
/**
* 对指定部门的当前人数进行更新
* @param deptno 更新的部门编号
* @param updateNum 更新的数量,该参数允许为负数,如果是负数,则更新为减少部门人数
* @return 更新的结果,成功返回true,失败返回false
* @throws SQLException
*/
public boolean updateCurrnum(Integer deptno,Integer updateNum)throws SQLException;
}
- 实现编写DeptDAOImpl子类,复写以上的抽象方法,并且还要复写一个findById()方法,这个方法在进行雇员的添加的时候用户判断要添加的部门是否有空位
public class DeptDAOImpl extends AbstractDAO implements IDeptDAO {
@Override
public List<Dept> findUnders() throws SQLException {
String sql = "SELECT deptno,dname,maxnum,currnum FROM dept WHERE CURRNUM < MAXNUM";
super.pstmt = super.conn.prepareStatement(sql);
List<Dept> under_Depts = new ArrayList<Dept>();
ResultSet rs = super.pstmt.executeQuery();
while(rs.next()){
Dept vo = new Dept();
vo.setDeptno(rs.getInt(1));
vo.setDname(rs.getString(2));
vo.setMaxnum(rs.getInt(3));
vo.setCurrnum(rs.getInt(4));
under_Depts.add(vo);
}
return under_Depts;
}
@Override
public boolean updateCurrnum(Integer deptno,Integer updateNum) throws SQLException {
String sql = "UPDATE dept SET currnum=currnum+"+updateNum+" WHERE deptno=?";
super.pstmt = super.conn.prepareStatement(sql);
super.pstmt.setInt(1,deptno);
return super.pstmt.executeUpdate() > 0;
}
@Override
public Dept findById(Integer id) throws SQLException {
String sql = "SELECT deptno,dname,maxnum,currnum WHERE deptno=?";
super.pstmt = super.conn.prepareStatement(sql);
super.pstmt.setInt(1,id);
ResultSet resultSet = super.pstmt.executeQuery();
Dept vo = null;
if(resultSet.next()){
vo = new Dept();
vo.setDeptno(resultSet.getInt(1));
vo.setDname(resultSet.getString(2));
vo.setMaxnum(resultSet.getInt(3));
vo.setCurrnum(resultSet.getInt(4));
}
return vo;
}
}
- 建立IEmpServiceBack接口,实现雇员的增加处理
- 在进行雇员追加的时候必须要求先列出所有的部门信息以及所有的职位等级信息.
package mao.shu.em.service.back;
import mao.shu.em.vo.Emp;
import java.util.Map;
public interface IEmpServiceBack {
/**
* 进行雇员增加时的准备信息提取操作,将增加雇员所需要的信息以一个Map集合的形式返回.
* @return 返回的信息中包含如下的内容 </br>
* 1. key=allUnderDepts value = 包含所有未满员的部门信息,使用IDeptDAP.findUnders()方法完成</br>
* 2. key = allLevels() value = 所有工资等级的数据,使用ILevelDAO.findAll()方法完成
* @throws Exception
*/
public Map<String,Object> addPre()throws Exception;
/**
* 进行添加一名雇员信息的操作,该操作会进行如下几步<br/>
* 1. 对当前操作的用户进行判断,判断当前用户是否具备有添加雇员的权限<br/>
* 2. 判断要添加的部门是否有空余位置<br/>
* 3. 判断添加的工资是否在所处的等级范围中<br/>
* 4. 对新雇员的信息进行保存<br/