项目工程框架:
一:建立各个所需的包。
- dao 操作数据库,
public User findUserByActiveCode(String activeCode) throws SQLException {
QueryRunner qr = new QueryRunner(C3P0Util.getDataSource());
return qr.query("select * from user where activecode=?",
new BeanHandler<User>(User.class), activeCode);
}
- domain javabean,对象实体类
- service 调用dao层对用户的增删改查并处理一些逻辑。所以在dao层只做操作数据库的事。
- factory 用来创建service,解耦,使用service更灵活,配合配置文件使用。有点像dubble
- web 一些servelt处理请求和响应。 调用service使用(验证码居然也包括在里面)
- filter 拦截器,目前主要用于拦截请求,并做编码操作
- util 工具包,C3P0Util(自己写的类),发送邮件啊,之类的
- exception (可以自定义异常)
二:注册
注册的页面实现准备好。
二话不说上来就是直接配置拦截器,把编码处理一下。拦截类是有,直接用。在web.xml中配置一下拦截器。
<filter>
<filter-name>EncodingFilter</filter-name>
<filter-class>com.itheima.product.web.filter.EncodingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>EncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
OK,以后就再也不用考虑编码的问题了。
观察注册表的值。 表单提交时<form action="${pageContext.request.contextPath}/register" method="post">
咱们表单提交的地址/register,所以写的servel的地址也应该在web.xml中注册一下。
<servlet>
<servlet-name>RegisterServlet</servlet-name>
<servlet-class>com.itheima.product.web.servlet.RegisterServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>RegisterServlet</servlet-name>
<url-pattern>/register</url-pattern>
</servlet-mapping>
所以开始写一个RegisterServlet.
RegisterServlet :
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//处理验证码
String ckcode = request.getParameter("ckcode");
String checkcode_session = (String) request.getSession().getAttribute("checkcode_session");
if(!checkcode_session.equals(ckcode)){//如果两个验证码不一致,跳回注册面
request.setAttribute("ckcode_msg", "验证码错误!");
request.getRequestDispatcher("/register.jsp").forward(request, response);
return ;
}
//获取表单数据
User user = new User();
try {
BeanUtils.populate(user, request.getParameterMap());
user.setActiveCode(UUID.randomUUID().toString());//手动设置激活码
//调用业务逻辑
UserService us = new UserService();
us.regist(user);
//分发转向
//要求用户激活后才能登录,所以不能把用户信息保存session中
//request.getSession().setAttribute("user", user);//把用户信息封装到session对象中
request.getRequestDispatcher("/registersuccess.jsp").forward(request, response);
}catch(UserException e){
request.setAttribute("user_msg", e.getMessage());
request.getRequestDispatcher("/register.jsp").forward(request, response);
return;
}catch (Exception e) {
e.printStackTrace();
}
}
咱们这个servlet,是用来把新注册的用户写入到数据库的。但是写入之前应该匹配一下验证码。
说到验证码:专门有个servlet生成,因此也要在web.xml中注册一下地址。然后我们在前端,把需要验证码的位置写上请求servelt的地址就好了。(注意哦,生成的验证码是存在session域中的。
request.getSession().setAttribute("checkcode_session", word);)差点忘了,我这个验证码是需要读取txt文件的
String path = getServletContext().getRealPath("/WEB-INF/new_words.txt");放在WEB-INF下面。
<tr>
<td style="text-align:right;width:20%;"> </td>
<td colspan="2" style="width:50%"><img
src="${pageContext.request.contextPath}/imageCode" width="180"
height="30" class="textinput" style="height:30px;" id="img" />
<a href="javascript:void(0);" onclick="changeImage()">看不清换一张</a>
</td>
</tr>
<script type="text/javascript">
function changeImage() {
document.getElementById("img").src = "${pageContext.request.contextPath}/imageCode?time="+ new Date().getTime();
}
</script>
如上,请求图片地址就是servlet地址。然后‘’看不清换一张‘’是一个onclick,这个方法也很简单啦,就是在请求一次,多带了time参数过去了,为的是刷新图片。
OK,验证码完结了。
继续讲咱们的RegisterServlet
那么提交表单后,咱们当然不能让她们直接插入数据了。通过request.getparameter(“activecode”)拿到表单写的验证码,
然后与从session域中取出验证码进行比对.如果错误,将验证码错误存在request域中,然后转发到当前页面,重新来。
request.setAttribute("ckcode_msg", "验证码错误!");
前端通过EL表达式取出来。
<td>${ckcode_msg }</td>
如果正确:那就插入呗。但是插入之前又得注意,咱们发送邮件的激活码还没有生成。因为表单提交中,不会有激活码生成。需要咱们手动生成。user.setActiveCode(UUID.randomUUID().toString());
嗯,然后可以安心插入数据了。之后,将user存入session域中。转发到注册成功的页面。(由于没有激活,所以咱们不让他登录)
Service层:userService:
因为我们需要注册,先写一个regist(user)方法。只需通过调用dao层方法,addUser方法就行了。如果出现异常,手动抛出一个异常(这个我好像没怎么用过,记下。)
抛出: catch (SQLException e) {
e.printStackTrace();
throw new UserException("注册失败!");
}
由于在service层抛出的这个异常,所以在servlet可以捕获这个异常,因为我们调用了service方法啊。
捕获:catch(UserException e){
request.setAttribute("user_msg", e.getMessage());
request.getRequestDispatcher("/register.jsp").forward(request, response);
return;
(小结:1.一般在发生异常,和错误或者不想让程序进行下去时,就用return。如RegisterServlet中的,出现验证码错误
2.像一般发生异常或者错误啊,就直接将错误存request域中,request.setAttribut.取也很好取,直接${域的名字}.)
Dao层:不愿记,里面涉及到DBUtils使用。用的使用直接拿别人代码改吧
public void addUser(User user) throws SQLException {
QueryRunner qr = new QueryRunner(C3P0Util.getDataSource());
String sql = "INSERT INTO USER(username,PASSWORD,gender,email,telephone,introduce,activecode,state,registtime) "
+ "VALUES(?,?,?,?,?,?,?,?,?)";
qr.update(sql, user.getUsername(), user.getPassword(),
user.getGender(), user.getEmail(), user.getTelephone(),
user.getIntroduce(), user.getActiveCode(), user.getState(),
user.getRegistTime());
}
三:发送邮件激活码
那么在哪里写激活邮件呢? 在service层中,adduser之后,发送激活邮件。带着邮箱地址,和想写的内容(里面包含激活码,所以说,激活码要在插入用户之前手动插入),发送。
String emailMsg = "注册成功,请<a href='http://www.product.com/activeServlet?activeCode="+user.getActiveCode()+"'>激活</a>后登录";
SendJMail.sendMail(user.getEmail(), emailMsg);
那之后呢?他怎么激活的?注意看,咱们给他发送的邮件里的地址,是一个servlet地址,带着参数激活码去的。
那就好办了,写一个servlet,获取到参数。调用service的激活方法activeUser(新建一个)。
service层:activeUser方法首先调用,通过激活码查找用户的方法。首先判断有没有这个用户,有的话然后调用另一个方法将staus状态改变,变为激活状态。
// 修改用户激活状态
public void activeCode(String activeCode) throws SQLException {
QueryRunner qr = new QueryRunner(C3P0Util.getDataSource());
qr.update("update user set state=1 where activecode=?", activeCode);
}
(我发现,那个抛出自定义异常,一般都在service层。不过也只能在这抛了,其他都不允许啊)
四:登录
注册都搞定了,登录就更不是问题了。
又是登录的jsp咯,自己写好。表单提交地址等
新建一个loginservelt,直接取出用户名和密码,调用service方法。service方法调用dao层,写一个通过用户名和密码查出用户的方法。servlet中查出了用户,这里开始注意,用户权限我们是在servlet里面判断的,如果不是管理员,则进入普通页面,是管理员进入另一个页面。这段代码用了path,解决了权限问题。
User user=userService.login(name, password);
String path="/index.jsp";
if ("admin".equals(user.getRole())) {
path="/admin/login/home.jsp";
}
request.getSession().setAttribute("user",user);
request.getRequestDispatcher(path).forward(request, response);
然后激活状态呢?我们在service层中判断。就是login方法中。先根据密码和用户名取出user,然后判断staus是否是激活状态,不是,抛出自定义异常。反正servlet接受。e.getMessage(),就是我们在service存入的消息。
catch (UserException e) {
// TODO Auto-generated catch block
e.printStackTrace();
request.setAttribute("user_msg", e.getMessage());
request.getRequestDispatcher("/login.jsp").forward(request, response);
(总结:抛出异常总是在service,异常信息也是这时候写入。然后之后servlet捕获异常时,将异常信息存入request域中,转发也是在这时候。也好理解,sevelt中才好处理这些啊,有request,处理转发和存入域中。)
嗯,因为servelt接受抛出自定义异常,总是会带有request域中消息,所以请把登录时错误消息取出来显示到前端页面,EL表达式。
实现30内自动登录和记住用户名:
前端页面:就是多了两个复选框。一个是记住用户名,一个是30天内登录。
<td><input type="checkbox" name="remname" value="true"
<c:if test="${cookie.remname != null }">
checked = 'checked'
</c:if>
/>记住用户名</td>
<td><input type="checkbox" name="autologin" value="true" />30天内自动登陆</td>
你看,记住用户名这是个复选框,并进行了判断。将从cookie中取值,如果有值就选上,没有就默认不选。
为什么非要从cookie中取值呢?因为你退出页面后,在进入还需要有这个勾选中啊,所以只能用cookie,你总不能用其他的域吧
像这种用户退出去在进来还有的,就是用cookie存。
那为啥这个30天登录没有if标签呢?因为你点了自动登录就看不到这个页面了。
UserService service = BasicFactory.getFactory().getInstance(UserService.class);
//1.获取用户名密码
String username = request.getParameter("username");
String password = MD5Utils.md5(request.getParameter("password"));
//2.调用Service中根据用户名密码查找用户的方法
User user = service.getUserByNameAndPsw(username,password);
if(user == null){
request.setAttribute("msg", "用户名密码不正确!");
request.getRequestDispatcher("/login.jsp").forward(request, response);
return;
}
//3.检查用户激活状态
if(user.getState() == 0){
request.setAttribute("msg", "用户尚未激活,请到邮箱中进行激活!");
request.getRequestDispatcher("/login.jsp").forward(request, response);
return;
}
//4.登录用户重定向到主页
request.getSession().setAttribute("user", user);
//--处理记住用户名
if("true".equals(request.getParameter("remname"))){
Cookie remnameC = new Cookie("remname",URLEncoder.encode(user.getUsername(),"utf-8"));
remnameC.setPath("/");
remnameC.setMaxAge(3600*24*30);
response.addCookie(remnameC);
}else{
Cookie remnameC = new Cookie("remname","");
remnameC.setPath("/");
remnameC.setMaxAge(0);
response.addCookie(remnameC);
}
//--处理30天内自动登陆
if("true".equals(request.getParameter("autologin"))){
Cookie autologinC = new Cookie("autologin",URLEncoder.encode(user.getUsername()+":"+user.getPassword(),"utf-8"));
autologinC.setPath("/");
autologinC.setMaxAge(3600*24*30);
response.addCookie(autologinC);
}
response.sendRedirect("/index.jsp");
URLEncoder.encode(user.getUsername()+":"+user.getPassword(),"utf-8") ,这个是用来解码的。因为用户名包含中文。
记住用户名,那么我们必须去前端显示了,从cookie中取出来。
<head>
<script type="text/javascript">
window.onload=function(){
var str = decodeURI('${cookie.remname.value}');
document.getElementsByName("username")[0].value = str;
}
</script>
</head>
本来这段js代码,需要写在最后,如果写在前面,getElements是获得不到的,因为页面还没加载过去。但是用了window.onload
可以在页面加载完毕,加载这个函数。 decodeURI('${cookie.remname.value}');将cookie中的值解码,注意cookie中是以map的
形式存值,所以要用cookie.remname.value的方式取值。document.getElementsByName("username")[0].value = str;然后直接
赋值就好了。我记得以前都是setAttribute赋值,但是这个节点的value可以直接赋值。
那30天登录怎么办呢?
做个自动登录的拦截器AutologinFilter
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
//1.只有未登录的用户才自动登录
if(req.getSession(false)==null || req.getSession().getAttribute("user")==null){
//2.只有带了自动登陆cookie的用户才自动登陆
Cookie [] cs = req.getCookies();
Cookie findC = null;
if(cs!=null){
for(Cookie c : cs){
if("autologin".equals(c.getName())){
findC = c;
break;
}
}
}
if(findC!=null){
//3.只有自动登录cookie中的用户名密码都正确才做自动登陆
String v = URLDecoder.decode(findC.getValue(),"utf-8");
String username = v.split(":")[0];
String password = v.split(":")[1];
UserService service = BasicFactory.getFactory().getInstance(UserService.class);
User user = service.getUserByNameAndPsw(username, password);
if(user!=null){
req.getSession().setAttribute("user", user);
}
}
}
//4.无论是否自动登陆都要放行
chain.doFilter(request, response);
}
1.只有未登录的用户才自动登录2.只有带了自动登陆cookie的用户才自动登陆3.只有自动登录cookie中的用户名密码都正确才做自动登陆(拦截所有请求)
感觉这个写的有点牛逼,很严谨。尤其是最后一个,还要去数据库验证一下。(因为可能人为的改动数据)所以所有涉及到cookie的,存取时都应该跟数据库打交道。之后存入session后就好了,登录时会自动判断是否seesion有值。
牛逼!
所以退出注销时也不能只销毁session,还有cookie
if(request.getSession(false)!=null){
request.getSession().invalidate();
//删除自动登陆cookie
Cookie autologinC = new Cookie("autologin","");
autologinC.setPath("/");
autologinC.setMaxAge(0);
response.addCookie(autologinC);
}
response.sendRedirect("/index.jsp");
request.getSession(true):若存在会话则返回该会话,否则新建一个会话。
request.getSession(false):若存在会话则返回该会话,否则返回NULL
getSession(boolean create)意思是返回当前reqeust中的HttpSession ,如果当前reqeust中的HttpSession 为null,当create为true,就创建一个新的Session,否则返回null; 简而言之: HttpServletRequest.getSession(ture)等同于 HttpServletRequest.getSession() HttpServletRequest.getSession(false)等同于 如果当前Session没有就为null;
所以说如果只是想判断下session是否存在,那么HttpServletRequest.getSession(false),如果用
request.getSession()或者request.getSession(true)(两个都一样),不好意思,没有你还新建一个。