这是笔者第一次写博客,由于数量较大,项目分为两篇文章,笔者力求各位看官和学子能够学会学懂,会在每段代码下面说明需要注意的地方和为什么用哪种方式写。如有文章层次不明晰的地方请多多指出,整个项目完整代码会放到第二章文末。
系列文章第二篇链接地址:
https://blog.csdn.net/alodonoa/article/details/107283410
一、先说说MVC和SpringMVC的异同
首先,MVC结构与Spring MVC架构不同,MVC不依赖于Spring,只是有与其类似的结构;Spring MVC必须依赖于Spring,SpringMVC是基于Spring上添加的开发框架。二者都是Model、View、Controller结构。其次,这是笔者第一篇博客,如果有文章层次不明晰的地方请多多指出。
一、本项目所使用的技术和软件以及环境
技术上,用的是MVC框架模式,JDBC连接,Session技术,以及过滤器Filter技术,还有最捞的JSP技术。
软件上,用的是IDEA2019版本,Navicat Premium。
环境上,笔者用的数据库是MySQL8.0.16,JDK是11LTS。后续我们会陈述与大家用的JDK8.0和MySQL5.x的不同和如何在代码里修改。
三、项目结构
先阐明项目的分布结构。下面没有截到的,都是JSP文件与IDEA的默认文件了。
四、正文
4.1登录和注册部分页面
话不多说,上图,图片为证。
这是注册页面。
这是登录页面。
4.2、登录和注册部分代码的讲解
4.2.1用户实体类
public class UserInfo {
private String userid;
private String username;
private String password;
private String contact;
这是用户的属性,这部分比较简单,且篇幅有限,就不多放了。需要的可以在第二篇文章文末自取。用户实体类里定义属性,这里我们全部设置为了String,对应数据库里的varchar,如果定义成int也可以,对应数据库里的tinyint。这里我也不设置主属性id的自增,当时做项目时候我想着用学号或或者手机号什么的来代替账号。定义好属性后需要有用户属性的全参构造,以及getter和setter方法,为了以防万一,再加上toString方法。
4.2.2注册部分Servlet层代码
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException,ServletException{
request.setCharacterEncoding("UTF-8");
String userid = request.getParameter("useridtext");
String username = request.getParameter("usernametext");
String password = request.getParameter("passwordtext");
String contact = request.getParameter("contacttext");
UserInfo userInfo = new UserInfo(userid,username,password,contact);
UserService userService = new UserServiceImpl();
System.out.println(userInfo);
//check if user information is existed in data table
int n ;
if(userService.ifExists(userid)){
try {
n = userService.doRegister(userInfo);
response.sendRedirect(request.getContextPath() + "/login.jsp");
return;
} catch (IOException e) {
e.printStackTrace();
}
}else {
request.setAttribute("errorMsg","注册失败,用户已经存在");
request.getRequestDispatcher("/welcome.jsp");
}
}
先说注册部分的后端,我们用doPost方法,因为前段用了post方法,所以这里必须保持一致。
为什么不用doGet?前段get方法会在URL地址上暴露输入的信息,比如用户名和账号,而post不会,post相对更加安全。
<table style="margin-left:40%">
<marquee width="200" scrolldelay="250">欢迎使用</marquee>
<tr>
<td>账号(请输入学号):</td>
<td><input name="useridtext" type="text" size="20" placeholder="StudentID"></td>
</tr>
<tr>
<td>登录名:</td>
<td><input name="usernametext" type="text" size="20" placeholder="your nickname"></td>
</tr>
<tr>
<td>密码:</td>
<td><input name="passwordtext" type="password" size="20" placeholder="number and character"></td>
</tr>
<tr>
<td>联系方式:</td>
<td><input name="contacttext" type="text" placeholder="phone number or email"></td>
</tr>
<tr>
<td>确认密码:</td>
<td><input name="passwordConfirm" type="checkbox"></td>
</tr>
</table>
(1)这两段代码块中,需要注意的是,doPost中getParameter方法所获取的字符串(“ueridtext”)与前端input标签中的name必须一致,比如后端是useridtext,那么name也必须useridtext,不能是userid或者其他的字符串,笔者深受其害,为此找了一晚上bug。
(2)其次说一下sendRedirect和getRequestDispatcher的区别在于,前者重定向时候不会发送数据,后者转发时候会携带数据。
4.2.3登录部分Servlet层代码
//check the username
if(userid==null || "".equals(userid)){
request.setAttribute("iderror", "用户名不能为空(JSP)!");
request.getRequestDispatcher("/login.jsp").forward(request, response);
return;
}
//check the password
if(password==null || "".equals(password)){
request.setAttribute("passwordnull", "密码不能为空(JSP)!");
request.getRequestDispatcher("/login.jsp").forward(request, response);
return;
}
//3.跳转:成功:success.jsp 失败: login.jsp
if(user!=null) {
try {
String cid = URLEncoder.encode(userid, StandardCharsets.UTF_8);
Cookie cookie1 = new Cookie("cid", cid);
Cookie cookie2 = new Cookie("upwd", password);
//set cookie
if ("yes".equals(remember)) {
cookie1.setMaxAge(60 * 60 * 10);
cookie2.setMaxAge(60 * 60 * 10);
} else {
cookie1.setMaxAge(0);
cookie2.setMaxAge(0);
}
response.addCookie(cookie1);
response.addCookie(cookie2);
HttpSession session1 = request.getSession();
session1.setAttribute("useridtext", userid);
response.sendRedirect(request.getContextPath() + "/operation.jsp");
return;
} catch (IOException e) {
e.printStackTrace();
}
}else{
request.setAttribute("error", "用户名或密码错误");
response.sendRedirect(request.getContextPath() + "/login_failure.jsp");
}
这是登录部分后端代码,同样用doPost,原因同上。
<table style="margin-left:40%">
<marquee width="200" scrolldelay="250">用户登录</marquee>
<tr>
<td>账号:</td>
<td><input type="text" size="21" name="useridtext" placeholder="userID" value="<%=userid%>"/></td>
</tr>
<tr>
<td>密码:</td>
<td><input type="password" name="passwordtext" size="21" value="<%=password%>"/></td>
</tr>
<tr>
<td>七天内免登录</td>
<td><input type="checkbox" name="memory" value="yes" <%=check%>></td>
</tr>
</table>
这是前端页面代码,如果登陆成功则会转发到操作页面,登陆失败则重定向到失败页面。需要注意的地方,也是name等同上面注册部分。
4.2.4封装数据库连接类DBHelper
我们把连接数据库的类DBHelper单独封装起来到工具包util包,这是为了减少代码量,而且在修改代码时候,不会大面积修改,不会牵扯到太多。这里的工具类可以万用,只需要修改端口号和 数据库URL地址。
public class DBHelper {
private static final String JDBC_DRIVER = "com.mysql.cj.jdbc.Driver";
private static final String url = "jdbc:mysql://localhost:数据库端口号/数据库名称?useUnicode=true&characterEncoding=UTF-8&useOldAliasMetadataBehavior=true&autoReconnect=true&serverTimezone=UTC";
private static final String username = "数据库用户名";
private static final String password = "数据库密码";
public static Connection getConnection() {
Connection connection =null;
try {
Class.forName(JDBC_DRIVER);
if(connection==null)
connection = DriverManager.getConnection(url, username, password);
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
return connection;
}
public static void closeAll(ResultSet rs, PreparedStatement ps, Connection connection){
try{
if(connection!=null){
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try{
if(rs!=null){
rs.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try{
if(ps!=null){
ps.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
这里需要注意的是,MySQL8.0.16和MySQL5.x的区别,8.0.16的url地址需要输入时间区域和字符编码集,8.0.16的数据库连接驱动是com.mysql.cj.jdbc.Driver,而5.x的url地址不需要时间区,连接驱动是com.mysql.jdbc.Driver。
4.2.5Dao层注册部分代码
try{
connection = DBHelper.getConnection();
String sql = "insert into userinfo (userid,username,password,contact) values(?,?,?,?)";
ps = connection.prepareStatement(sql);
ps.setString(1,user.getUserid());
ps.setString(2,user.getUsername());
ps.setString(3,user.getPassword());
ps.setString(4,user.getContact());
n= ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}finally {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
这是Dao层注册功能的主要代码,我们只说思路以及需要注意的地方,我们已经封装类DBHelper类,因此直接调用该类连接数据库,然后写SQL语句,然后用connection.prepareStatement(sql);
来对语句进行封装;ps.setString(1,user.getUserid());
后面的这句代码,对数据库进行操作,这里需要注意的是,对数据库操作的若干句代码,其顺序一定要与上面SQL语句的问号顺序对应,就是说,第一个问号是id的,那么setString就先操作id,如果顺序不对应会导致操作数据库失败。
4.2.6Dao层登录部分代码
try{
connection = DBHelper.getConnection();
String sql = "select * from userinfo where userid = ? and password = ? ";
ps = connection.prepareStatement(sql);
ps.setString(1,userid);
ps.setString(2,password);
rs = ps.executeQuery();
while (rs.next()){
userid = rs.getString("userid");
String username = rs.getString("username");
password = rs.getString("password");
String contact = rs.getString("contact");
userInfo = new UserInfo(userid,username,password,contact);
}
这是Dao层登录部分代码,需要注意的地方与Dao层注册部分相同,唯一不同的是Result的对象rs,查询时候需要用Result对查询到的数据进行封装处理,因此我们调用了这个方法rs = ps.executeQuery();
这句代码从数据库里进行查找,如果有需要数据,则继续。当rs不为空时候,我们进行下面的匹配操作,并对数据进行封装处理。
4.2.7Service层注册部分代码
UserDao userDao = new UserDaoImpl();
@Override
public int doRegister(UserInfo user) {
/* UserInfo userInfo = null;
try{
userInfo = userDao.doRegister(user);
} catch (Exception e) {
e.printStackTrace();
}
return userInfo;*/
return userDao.doRegister(user);
}
这是service层的注释功能代码,这里被注释掉的代码,其功能与return语句的代码功能完全相同,笔者在做项目的时候为了自己能够对更多知识点进行巩固,或者说是对return语句的一个更详细的拆解,故意这样做,各位看官和学子大可不必再去写被注释掉的代码。
4.2.8Service层登录部分代码
@Override
public UserInfo doLogin(String userid, String password) {
return userDao.doLogin(userid,password);
}
这是Service层的登录部分代码,逻辑比较简单,因此直接调用dao层的方法,上面的注册部分也是如此。
4.2.9设置登录过滤器LoginFilter类
到这里其实我们已经可以结束了,但是为了让项目更加贴近实际,我们设置登录过滤器。这里还需要对Web目录下,WEB-INF目录里的web.xml文件进行配置,我们稍后做解释。话不多说,上图!
我们注意这是登录界面,我们还没有进行登录操作,URL地址还是login.jsp。
我们输入operation.jsp或者随便输入其他项目里的URL,如果没登录,都会强行跳转到这个登录页面来提醒用户登录。下面放代码
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
HttpSession session = request.getSession();
//configure the paths that would not filtering
String noLoginPaths = config.getInitParameter("noLoginPaths");
//check if the character is legal
String charset = config.getInitParameter("charset");
if(charset==null){
charset = "UTF-8";
}
request.setCharacterEncoding(charset);
//search for resources from noLoginPaths
if(noLoginPaths!=null){
String[] strArray = noLoginPaths.split(";");
for (int i = 0; i < strArray.length; i++) {
if(strArray[i]==null || "".equals(strArray[i]))continue;
if(request.getRequestURI().contains(strArray[i])){
filterChain.doFilter(servletRequest, servletResponse);
return;
}
}
}
//receive userid from web page and check if userid is right
if(session.getAttribute("useridtext")!=null){
filterChain.doFilter(servletRequest, servletResponse);
}else {
response.sendRedirect("login.jsp");
}
}
这个是主要代码部分,其中noLoginPaths是webxml里我们自定义的不需要进行登录操作的页面,如注册页面register.jsp等等。因为request和response类型不同,我们还需要分别进行强制转换,然后对web.xml文件里设置的不过滤的 文件进行循环遍历,如果有该文件,在不过滤,如果没有改文件,则进行过滤操作。这里需要注意的是,useridtext也要与login.jsp的useridtext对应,否则是获取不到数据的。
<filter>
<filter-name>LoginFilter</filter-name>
<filter-class>filter.impl.LoginFilterImpl</filter-class>
<init-param>
<param-name>noLoginPaths</param-name>
<param-value>login.jsp;login_failure.jsp;LoginServlet;welcome.jsp;error.jsp;register.jsp</param-value>
</init-param>
<init-param>
<param-name>charset</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>LoginFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
这个是web.xml文件,与上面所述对应,param-name我们可以自定义,filter-name是我们定义的过滤器类LoginFilter。下面的映射mapping,filter-name同理;url-parrten,考虑到写的页面以后会有许多许多,因此我们设置为全部过滤,并且在上面noLoginPaths里设置特定的不过滤的页面。这就好比,除了小明,其他人都要去参与某个活动这个意思。
五、总结
在这里总结一下遇到的问题,希望能够帮到需要的人:
(1)MySQL版本问题,MySQL8.0.16与MySQL5.x的不同之处在于,8.0.16的驱动是com.mysql.cj.jdbc.Driver
,需要加上cj,8.0.16的URL地址是jdbc:mysql://localhost:数据库端口号/数据库名称?useUnicode=true&characterEncoding=UTF-8&useOldAliasMetadataBehavior=true&autoReconnect=true&serverTimezone=UTC
数据库端口号是我们MySQL的端口号如3307和3306,数据库名称是我们自定义建立的数据库的名称,比如我的是bookdbms。这个链接可以万用,只要是8.0.x版本的MySQL数据库,修改一下端口号和数据库名称就可以用,笔者为此烦扰了很久,这也是比较大众化的一个问题。希望看到这篇文章的朋友一定要注意这个,也希望我能够帮到大家!
(2)Servlet层getParameter的字符串一定与前端页面的字符串相同。包括比如我们写了个超链接<a href = "../xxxxx/xxxx?userid=xxx>"
这里的userid也要与getParameter里的字符串相同,否则在转发时候会获取不到数据,成为空值。
(3)JDK的问题,JDK8与DJK11LTS并没有太大的不同,我们平时用还是可以的。其次,由于Oracle公司每半年更新一次,我们就只用最新版本的上一版本就好了 ,比如笔者之前下载时候有了JDK13,但是为了出错能够得到帮助和解决,就用了比较旧的JDK11LTS,11LTS是长期维护 的版本,比较稳定。笔者这里建议大家不要用最新版本当小白鼠了哈O(∩_∩)O~
注释:
(1)这篇文章所写的内容,完全可以独立于整个项目之外,可以当做独立的登录和注册来使用。
(2)这是笔者第一次写博客,希望能够帮到大家,同时也希望各位社区的朋友们,能够尊重知识主权,如果需要转载,请说明出处。
(3)各位如果觉得写的好的话,可以打赏一下哈,不嫌多不嫌少,多多捂脸⁄(⁄ ⁄•⁄ω⁄•⁄ ⁄)⁄
(4)如果有看官学子们需要下载的话,在系列文章第二篇文末,笔者会附上下载链接。