ssm项目part1:首页、登入、退出

JAVA项目(使用SSM实现)各部分详细分析(CRM)|| part1

项目结构:

在这里插入图片描述

1、首页功能

首页功能比较简单,可以在springmvc.xml文件中设:视图控制器,或者写一个controller实现这个功能。

 <!--
	视图控制器:为当前的请求直接设置视图名称实现页面跳转,这就不需要再controller里面多写一个方法,来实现跳转到首页了!
	/ 表示从项目名开始输入的内容,即tomcat配置的那个开始  http://localhost:8080/crm 都省略

  若设置视图控制器,则只有视图控制器所设置的请求会被处理,其他的请求将全部404
  此时必须在配置一个标签:<mvc:annotation-driven />
  -->
<mvc:view-controller path="/" view-name="index"/>

2、用户登入

2.1 设计要求

  1. 用户名和密码不能为空
  2. 用户名或者密码错误,用户已过期,用户状态被锁定,ip受限 都不能登录成功
  3. 登录成功之后,所有业务页面显示当前用户的名称
  4. 实现10天记住密码
  5. 登录成功之后,跳转到业务主页面
  6. 登录失败,页面不跳转,提示信息

2.2 设计思路:

1. 前端提交的内容只有三个

用户名、密码、是否十天免登入(前端首页那边只能提交这三个东西); 这样一来要想到:controller那边只要接收这三个参数(复习使用map传递数据),并根据其中的一些参数查数据库(用户名、密码)

2. 配置mybatis-generator

能自动生成对应的实体类、mapper和xml
只要配置一个xml文件(和properties文件),在文件里面填写:实体类、mapper和xml自动生成后放的位置。注意对应表进行生成后,将该表注释,否则下次生成其他表的时候会覆盖,自己修改的mappe和xml就被覆盖了!

<javaModelGenerator targetPackage="cn.edu.uestc.crm.settings.domain"
   					targetProject="D:\\JAVA\\SSM\\CRM\\code\\crm-project\\crm\\src\\main\\java">

	<!-- 是否允许子包,即targetPackage.schemaName.tableName -->
	<property name="enableSubPackages" value="false"/>
	<!-- 是否对model添加 构造函数 true添加,false不添加-->
	<property name="constructorBased" value="false"/>
	<!-- 是否对类CHAR类型的列的数据进行trim操作 -->
	<property name="trimStrings" value="true"/>
	<!-- 建立的Model对象是否 不可改变  即生成的Model对象不会有 setter方法,只有构造方法 -->
	<property name="immutable" value="false"/>
</javaModelGenerator>
<table tableName="tbl_clue" domainObjectName="Clue"
   enableCountByExample="false" enableUpdateByExample="false"
   enableDeleteByExample="false" enableSelectByExample="false"
   selectByExampleQueryId="false">
</table>

3. 根据用户名密码查数据库

想法是封装为一个对象RetObj,并以json的形式返回(在ajax请求中设置dataType:'json’即可,其实很简单的)。将返回信息存到一个类中,该类的属性有:

private String code;//处理成功获取失败的标记:1---成功,0---失败
private String message;//提示信息
private Object retData;//其它数据(以后可能用到)

注:不将查询结果信息存到map而存到对象的原因是:
map效率比较低,它又得计算下标(数组那边:key通过hashcode得到哈希值,哈希值又通过算法转为下标),又得equals找链表

4. 具体查询过程的逻辑(满足设计要求2等)

如何设计要求2?要求2中的需要很多在数据库那边就封装好了,查出来返回一个对象到controller,再判断对象对应的属性是否满足要求。如果其中一个不满足要求,那上面返回给前端的类RetObj, code=0,message设置为对应属性哪个不满足要求了…

@RequestMapping("/settings/qx/user/login.do")
public @ResponseBody Object login(String loginAct, String loginPwd, String isRemPwd, HttpServletRequest request, HttpServletResponse response, HttpSession session){
    
    
	//封装参数,注意使用泛型,每个Obj都要又名字(key)
	Map<String,Object> map=new HashMap<>();
	map.put("loginAct",loginAct);
	map.put("loginPwd",loginPwd);
	//调用service层方法,查询用户
	User user=userService.queryUserByLoginActAndPwd(map);
	
	//根据查询结果,生成响应信息
	ReturnObject returnObject=new ReturnObject();
	if(user==null){
    
    
	   //登录失败,用户名或者密码错误
	   returnObject.setCode(Contants.RETURN_OBJECT_CODE_FAIL);
	   returnObject.setMessage("用户名或者密码错误");
	}else{
    
    //进一步判断账号是否合法
	   //user.getExpireTime()   //2019-10-20
	   //        new Date()     //2020-09-10
	   if(DateUtils.formateDateTime(new Date()).compareTo(user.getExpireTime())>0){
    
    
	       //登录失败,账号已过期
	       returnObject.setCode(Contants.RETURN_OBJECT_CODE_FAIL);
	       returnObject.setMessage("账号已过期");
	   }else if("0".equals(user.getLockState())){
    
    
	       //登录失败,状态被锁定
	       returnObject.setCode(Contants.RETURN_OBJECT_CODE_FAIL);
	       returnObject.setMessage("状态被锁定");
	   }else if(!user.getAllowIps().contains(request.getRemoteAddr())){
    
    
	       //登录失败,ip受限
	       returnObject.setCode(Contants.RETURN_OBJECT_CODE_FAIL);
	       returnObject.setMessage("ip受限");
	   }else{
    
    
	       //登录成功
	       returnObject.setCode(Contants.RETURN_OBJECT_CODE_SUCCESS);
	
	       //把user保存到session中
	       session.setAttribute(Contants.SESSION_USER,user);
	
	       //如果需要记住密码,则往外写cookie
	       if("true".equals(isRemPwd)){
    
    
	           Cookie c1=new Cookie("loginAct",user.getLoginAct());
	           c1.setMaxAge(10*24*60*60);
	           response.addCookie(c1);
	           Cookie c2=new Cookie("loginPwd",user.getLoginPwd());
	           c2.setMaxAge(10*24*60*60);
	           response.addCookie(c2);
	       }else{
    
    
	           //把没有过期cookie删除
	           Cookie c1=new Cookie("loginAct","1");
	           c1.setMaxAge(0);
	           response.addCookie(c1);
	           Cookie c2=new Cookie("loginPwd","1");
	           c2.setMaxAge(0);
	           response.addCookie(c2);
	       }
	   }
	}

return returnObject;
}

5. 前端工作

5.1 解决设计要求1:用户名和密码不能为空

  • 如果用户名密码为空,直接不让提交请求,以免给后台造成太大的负担,不为空才提交ajax请求,继续下一步去后台查数据库。
  • 前端一共三个输入需要处理:用户名(需要处理空格问题)、密码、十天免登入。
  • 使用ajax异步请求,所以在按钮界面上,不能使用submit(会直接提交同步请求),应该使用button,设置id,之后使用jQuery的id选择器对该id进行函数封装处理。
  • 注意细节:这里的代码实际是JavaScript弱类型语言,所有比较都是使用==进行比较,而在java中字符串比较是使用.equals()进行比较
  • 前端这边完成后,进行ajax访问后,如果数据成功,那应该进行同步跳转,跳到CRM系统里面去,这个比较简单,使window.location.href="workbench/index.do"完成跳转

5.2 前端提交请求到后端查数据库期间,显示:正在努力验证…

在登入过程中去查询数据库,返回ajax前会有一段空档期时间,这时候最好提示一下 “正在努力登入之类的信息”,这段信息既要在点击按钮之后,又要在查询完信息之前,使用:

//当ajax向后台发送请求之前,会自动执行本函数;
//该函数的返回值能够决定ajax是否真正向后台发送请求:
//如果该函数返回true,则ajax会真正向后台发送请求;否则,如果该函数返回false,则ajax放弃向后台发送请求。
beforeSend:function () {
		$("#msg").text("正在努力验证....");
		return true;
 }

5.3 前端登入页面代码

$(function () {
    
    
	//给整个浏览器窗口添加键盘按下事件
	$(window).keydown(function (e) {
    
    
		//如果按的是回车键,则提交登录请求
		if(e.keyCode==13){
    
    
			$("#loginBtn").click();
		}
	});
	//给"登录"按钮添加单击事件
	$("#loginBtn").click(function () {
    
    
		//收集参数
		var loginAct=$.trim($("#loginAct").val());
		var loginPwd=$.trim($("#loginPwd").val());
		var isRemPwd=$("#isRemPwd").prop("checked");
		//表单验证
		if(loginAct==""){
    
    
			alert("用户名不能为空");
			return;
		}
		if(loginPwd==""){
    
    
			alert("密码不能为空");
			return;
		}
		//显示正在验证
		//$("#msg").text("正在努力验证....");
		//发送请求
		$.ajax({
    
    
			url:'settings/qx/user/login.do',
			data:{
    
    
				loginAct:loginAct,
				loginPwd:loginPwd,
				isRemPwd:isRemPwd
			},
			type:'post',
			dataType:'json',
			success:function (data) {
    
    
				if(data.code=="1"){
    
    
					//跳转到业务主页面
					window.location.href="workbench/index.do";
				}else{
    
    
					//提示信息
					$("#msg").text(data.message);
				}
			},
			//下面的代码思想注意学习
			beforeSend:function () {
    
    //当ajax向后台发送请求之前,会自动执行本函数;
				                    //该函数的返回值能够决定ajax是否真正向后台发送请求:
								    //如果该函数返回true,则ajax会真正向后台发送请求;否则,如果该函数返回false,则ajax放弃向后台发送请求。
				$("#msg").text("正在努力验证....");
				return true;
			}
		});
	});
});

6.代码可移植性优化

在写代码时,经常会遇到写死一个字符串的情况:

  • 年月日的格式:yyyy-MM-dd HH:mm:ss如果有一天要改为 yyyy/MM/dd的形式那要在整个工程中改就太麻烦了,写一个Utils,下次改只需要改这个就行(里面的方法使用静态方法,直接用类名去调用)
  public static String formateDateTime(Date date){
    
    
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String dateStr=sdf.format(date);
        return dateStr;
    }

  • 常量也类似:比如在controller封装一个类返回给前端里面规定了一个属性,值=1的时候代表登入成功,值=0的时候代表失败,前端ajax进行处理。如果有一天:规定返回0000是登入失败,返回1111是登入成功怎么办? 只要在这里改就行了。不用整个项目去找。
  • 常量:登入成功后,进入页面,需要在顶上显示:欢迎xxx用户。这个功能中,需要使用session.setAttribute(“key”,user); 这里key的名字是一个字符串,不能写死,以后可能改掉!所以也写为常量。
public static final String RETURN_OBJECT_CODE_SUCCESS="1";//成功
public static final String RETURN_OBJECT_CODE_FAIL="0";//失败

//保存当前用户的key
public static final String SESSION_USER="sessionUser";

//备注的修改标记
public static final String REMARK_EDIT_FLAG_NO_EDITED="0";//0---没有修改过
public static final String REMARK_EDIT_FLAG_YES_EDITED="1";//1--已经被修改过

7.实现10天记住密码

  • 主要思路:前端复选框选中记住密码,后端收到后,在登入成功后的代码中,设置cookie。

  • 注意一个逻辑,用户点击了"十天记录密码",我们设定了cookie,如果下一次用户再登入的时候(复选框也要默认打勾),把复选框取消了,那这时候以前设置的cookie也要删除!还有如果点击了记住密码,下次打开这个页面,记录密码也是默认勾上的!
    -----实现方法:如果上次点击登入了,那cookie就存在,使用jstl标签库就可以读取到,如果有cookie,那就checkbox 设置为check状态。
    <c:if test="${not empty cookie.loginActCookie and not empty cookie.loginPwdCookie}"> <input type="checkbox" id="isRemPwd" checked> </c:if>

  • request是来到服务器的数据,response是从服务器响应到浏览器的数据。

1、使用response:response.addCookie(c1)将cookie响应到浏览器去
2、使用request:
2.1、request.getRemoteAddr()可以用于获取用户的ip地址等。
2.2、request.getCookie 浏览器将还没过期的cookie响应到服务器进行处理,然后再返回给前端,当然这种办法比较麻烦,可以使用el表达式(以前都是在作用域中获取,现在在cookie中获取:${cookie.loginAct.val},类似前面的sessionScope…),直接在前端解析出cookie,对文本框进行赋值。

代码如下:

//如果需要记住密码,则往外写cookie
  if("true".equals(isRemPwd)){
    
    
        Cookie c1=new Cookie("loginAct",user.getLoginAct());
        c1.setMaxAge(10*24*60*60);
        response.addCookie(c1);
        Cookie c2=new Cookie("loginPwd",user.getLoginPwd());
        c2.setMaxAge(10*24*60*60);
        response.addCookie(c2);
    }else{
    
    
        //把没有过期cookie删除
        Cookie c1=new Cookie("loginAct","1");
        c1.setMaxAge(0);
        response.addCookie(c1);
        Cookie c2=new Cookie("loginPwd","1");
        c2.setMaxAge(0);
        response.addCookie(c2);
    }

前端:使用jstl标签库还有el表达式

<div class="form-group form-group-lg">
		<div style="width: 350px;">
			<input class="form-control" id="loginAct" type="text" value="${cookie.loginAct.value}" placeholder="用户名">
		</div>
		<div style="width: 350px; position: relative;top: 20px;">
			<input class="form-control" id="loginPwd" type="password" value="${cookie.loginPwd.value}" placeholder="密码">
		</div>
		<div class="checkbox"  style="position: relative;top: 30px; left: 10px;">
			<label>
				<c:if test="${not empty cookie.loginAct and not empty cookie.loginPwd}">
					<input type="checkbox" id="isRemPwd" checked>
				</c:if>
				<c:if test="${empty cookie.loginAct or empty cookie.loginPwd}">
					<input type="checkbox" id="isRemPwd">
				</c:if>
				 十天内免登录
			</label>
			&nbsp;&nbsp;
			<span id="msg" style="color: red"></span>
		</div>
		<button type="button" id="loginBtn" class="btn btn-primary btn-lg btn-block"  style="width: 350px; position: relative;top: 45px;">登录</button>
	</div>

8.登入后:所有业务页面显示当前用户的名称

  • 需求:登入成功后,进入页面,需要在顶上显示:欢迎xxx用户。
  • 解决思路:把控制层(controller)代码中处理好的数据传递到视图层(jsp),使用作用域传递:四个作用域非常重要)

8.1 作用域

– pageContext:用来在同一个页面的不同标签之间传递数据。
– request:在同一个请求过程中间传递数据。(使用model.setAttribute()效果一样,都是request作用域)
– session: 同一个浏览器窗口的不同请求之间传递数据。一般一个用户是一个窗口(一个用户登入一次)
application:所有用户共享的数据,并且长久频繁使用的数据。

  • session获取方式:

void HttpSession.setAttribute(String name, Object value) 向session中保存信息

HttpSession HttpServletRequest.getSessio() 获取当前请求所在的session的对象。

  • 具体做法:
    – 在登入成功后,session.setAttribute(“key”,value); 之后在前端取出,显示到整体的页面上面。(session:同一个浏览器 窗口 可对应多个请求!)
后端:(在访问数据库,验证用户名密码ip状态等等通过后就设置,和cookie位置一样)
session.setAttribute(Contants.SESSION_USER,user);

前端:成功登入后在index.jsp页面上设置
 ${sessionScope.sessionUser.name}

3、安全退出

安全退出系统的主要思想是:1、清空session域(invalidate()方法),session对应的是多个request。2、清空cookie(sexMaxAge(0)方法)。思路就是专门写一个Controller方法,当页面点击“安全退出的时候”,进行上述操作,最后跳转到登入页面。

3.1 请求转发:

对应的是一次请求;浏览器上的URL不变

3.2 重定向:

对应的是两次请求(把url发送到前台,让前台再发送一次请求)。

  • 什么时候使用请求转发,什么时候使用重定向?
    第一个servlet没处理完,使用请求转发;处理完了,使用重定向
    如果使用请求转发,刷新页面的话,还是访问Controller的RequestMapping对应的地址进行操作,但是重定向的话,就是一个新的页面了,再刷新也是对这个新页面的刷新,我们希望用户退出后url显示登入页面的url,而不是logout的url(转发的话不变),而且刷新的时候不要重复去执行清空session、cookie操作,只简单地刷新登入页面,所以这里使用重定向

  • 这里发送同步请求,同步请求三个方式:地址栏(window.location.href=“对应的controller,或者页面”)、form表单,超级链接

  • 代码如下

@RequestMapping("/settings/qx/user/logout.do")
    public String logout(HttpServletResponse response, HttpSession session){
    
    
        //两件事,清空session、cookie
        Cookie actCookie = new Cookie("loginActCookie","1");
        Cookie pwdCookie = new Cookie("loginPwdCookie","1");
        actCookie.setMaxAge(0);
        pwdCookie.setMaxAge(0);

        response.addCookie(actCookie);
        response.addCookie(pwdCookie);

        session.invalidate();

        return "redirect:/"; //回到首页,自动拼接前后缀后,首页的jsp也会自动跳转(之前写的了,复习)
    }

4、登入验证

现在遇到这样的一个问题:上述代码在写的时候,页面全部都是通过访问对应的controller,之后使用controller的返回值进行前后缀拼接从而跳转的。也就是说登入后的页面可以通过访问对应的controller进行跳转,而不是说因为在web-inf下面就不能被访问(比如成功登入的页面就是ajax获取成功请求后访问一个controller,然后跳转到index.jsp页面的,如果直接记住那个controller的url,也可以直接跳转到目标页面),这就存在风险。
解决思路:配置过滤器、拦截器。

4.1 要求

登录验证.
用户访问任何业务资源,都需要进行登录验证.
只有登录成功的用户才能访问业务资源
没有登录成功的用户访问业务资源,跳转到登录页面

4.3 实现分析

  • 什么样的类能够在用户访问目标资源之前先执行?----过滤器、拦截器
    其中过滤器比较麻烦,且功能不足,需要实现接口Filter,重写方法(init…doFilter…destroy…),然后在web.xml 中进行配置,这里使用拦截器interceptor实现。
  • 拦截器:
    a)提供拦截器类:
 ....implements HandlerInterceptor{
	  --preHandle  (请求到达目标资源之前执行) 只有它有返回值,返回true就放行,false就拦截
	  --postHandle (执行目标资源之后执行)
	  --afterHandle (响应回去后,最后执行)
      }

b)配置拦截器:springmvc.xml

  • 当初用户登入成功后干了三件事:很明显,使用session来解决当前问题。
//运行登入
 retObj.setCode(Constants.RETURN_OBJECT_CODE_SUCCESS);
 //成功登入以后,在页面左上角需要显示: 欢迎xxx用户,是针对多个不同请求的操作,使用session~
 session.setAttribute(Constants.SESSION_USER,user);//sessionUser这个字符串名字不要写死,使用常量!
 //记住密码复选框的处理
 if ("true".equals(isRemPwd)){
    
    
     Cookie actCookie = new Cookie("loginActCookie",user.getLoginAct());
     Cookie pwdCookie = new Cookie("loginPwdCookie",user.getLoginPwd());
     //十天
     actCookie.setMaxAge(10*24*60*60);
     pwdCookie.setMaxAge(10*24*60*60);
     //传前端
     response.addCookie(actCookie);
     response.addCookie(pwdCookie);
 }else...

4.4 具体实现

  • 配置文件(在springmvc.xml中进行配置)
<mvc:interceptors>
    <mvc:interceptor>
        <!--
            配置拦截的请求
            之前说controller的RequestMapping都有对应的命名规范,这里就体现出了优势!
            注意一个*代表一层子目录,两个**代表任意层子目录
        -->
        <mvc:mapping path="/settings/**"/>
        <mvc:mapping path="/workbench/**"/>
        <!--配置排除拦截的请求(优先级高)-->
        <mvc:exclude-mapping path="/settings/qx/user/toLogin.do"/>
        <mvc:exclude-mapping path="/settings/qx/user/login.do"/>
        <!--拦截器类-->
        <bean class="com.bjpowernode.crm.settings.web.interceptor.LoginInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>
  • LoginInterceptor
public class LoginInterceptor implements HandlerInterceptor {
    
    
    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
    
    
        //如果用户没有登录成功,则跳转到登录页面
        HttpSession session=httpServletRequest.getSession();
        User user=(User) session.getAttribute(Contants.SESSION_USER);
        if(user==null){
    
    
            //重定向时,url必须加项目的名称(springmvc会自动加,所以写一个斜杆就行,这个倒是可以不用),如果是请求转发那也不用
            httpServletResponse.sendRedirect(httpServletRequest.getContextPath());//获取的结果就算   /上下文
            return false;
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
    
    

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
    
    

    }
}

猜你喜欢

转载自blog.csdn.net/YiGeiGiaoGiao/article/details/128506607