# BookShop **Repository Path**: fowner/bookshop ## Basic Information - **Project Name**: BookShop - **Description**: 通过运用JavaWeb三大组件组件之servlet,Filter进行JavaWeb项目开发。 - **Primary Language**: Java - **License**: GPL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 65 - **Forks**: 6 - **Created**: 2020-11-12 - **Last Updated**: 2025-08-20 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 书城项目 ## 概要 ``` 该项目主要实现了用户的注册和登录功能。 通过使用Servlet,JavaEE三层(Web,Service,Dao层),以及Web关联的一些基础知识进行项目的开发。 项目中还涉及到一些常用的工具类: 例如text/html 数据库连接池工具:druid 数据库操作工具:commons-dbutils ``` ## 基本环境说明 + Maven 3.6.1(jar管理工具) + Tomcat 8.5.59(服务器) + JDK 1.8 + JavaEE 8.0 + MySql 5.7.29(数据库管理系统) + IDEA 2019免费版(开发工具) + druid 1.2.3(数据库连接池) + commons-dbutils 1.7 (数据库操作工具) + kaptcha 2.3.2(验证码) + Gson 2.8.6(Json支持) 注:所有依赖参考pom.xml ## 构建静态网页 + 新建Maven项目webapp模型 + 把静态网页粘贴到webapp目录下 + pages文件夹 + static文件夹 + index.html文件 + web.xml中配置默认启动画面 ```xml index.jsp index.html index.htm ``` + 确认静态页面的联动 注:注册的验证已经完成 ## 实现用户注册和登录功能 ### 创建src目录 + web层 com.dream.servlet + service层 + service接口包 com.dream.service + service接口实现包 com.dream.service.impl + dao层 + dao接口包 com.dream.dao + dao接口实现类包 com.dream.dao.impl + 实体对象 com.dream.bean + 工具包 com.dream.utils + 测试包(使用Maven的测试文件夹) test ### 编码的流程 + 创建书城的数据库和表结构并把admin数据创建好 ```mysql-sql //创建字符集为utf-8的BookShop数据库 CREATE SCHEMA `BookShop` DEFAULT CHARACTER SET utf8; //创建用户表 create table `BookShop`.t_user( `id` int primary key auto_increment, `username` varchar(20) not null unique, `password` varchar(32) not null, `email` varchar(200), `registtime` date not null, `updatetime` varchar(255) ); //插入管理员的数据 insert into t_user(`username`,`password`,`email`,`registtime`) values ('admin','admin','admin@test.com',current_date); //查询插入后结果 select * from t_user; ``` + 创建与数据库表对应的JavaBean对象 ```java package com.dream.bean; /** * @author 匠人码农 * @date 2020/11/11 8:19 * 概要: * 用户信息类 */ public class User { //ID private Integer id; //用户名 private String userName; //密码 private String password; //邮件地址 private String email; //创建时间 private String registTime; //更新时间 private String updateTime; //无参构造器 public User() { } //有参数构造器 public User(Integer id, String userName, String password, String email, String registTime, String updateTime) { this.id = id; this.userName = userName; this.password = password; this.email = email; this.registTime = registTime; this.updateTime = updateTime; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getRegistTime() { return registTime; } public void setRegistTime(String registTime) { this.registTime = registTime; } public String getUpdateTime() { return updateTime; } public void setUpdateTime(String updateTime) { this.updateTime = updateTime; } } ``` + 编写工具类JdbcUtil + 需要的jar包依赖 ```xml mysql mysql-connector-java 5.1.47 com.alibaba druid 1.2.3 junit junit 4.11 test ``` + 创建jdbc.properties属性配置文件 ```properties #用户名 username=root #密码 password=admin2020 #数据库的url地址 url=jdbc:mysql://localhost:3306/BookShop #jdbc驱动 driverClassName=com.mysql.jdbc.Driver #连接池初始连接初始个数 initialZize=5 #连接池最大连接数 maxActive=10 ``` + 编写JdbcUtils工具类 ```java package com.dream.utils; import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.pool.DruidDataSourceFactory; import java.io.InputStream; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; /** * @author 匠人码农 * @date 2020/11/11 8:40 * 概要: * jdbc工具类 */ public class JdbcUtils { //数据库连接池 public static DruidDataSource dataSource; /* * 创建数据库连接池静态代码块 */ static { try { //创建properti对象 Properties properties = new Properties(); //获取jsbc配置文件数据流 InputStream inputSteam = JdbcUtils.class.getClassLoader().getResourceAsStream("jdbc.properties"); //导入jdbc配置文件 properties.load(inputSteam); //创建数据库连接池 dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties); } catch (Exception e) { e.printStackTrace(); } } /** * 获取数据库连接池中连接 */ /** * 获取数据库连接池中连接 * @return 如果不为null,则获取数据库连接成功.
* 如果为null,则获取数据库连接失败.
*/ public static Connection getConnection(){ Connection conn = null; try { conn = dataSource.getConnection(); } catch (SQLException e) { e.printStackTrace(); } return conn; } /** * 关闭数据库连接 */ public static void close(Connection conn){ //连接不为null的话,关闭连接。 if(conn != null){ try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } } ``` + 工具类JdbcUtils测试 ```java package com.dream.utils; import org.junit.Test; import java.sql.Connection; /** * @author 匠人码农 * @date 2020/11/11 10:18 * 概要: * JdbcUtils测试类 */ public class JdbcUtilsTest { @Test public void testJdbcUtils() { for(int i = 0;i < 20 ;i++){ //获取连接 Connection conn = JdbcUtils.getConnection(); //打印连接 System.out.println(conn); //关闭连接 JdbcUtils.close(conn); } } } ``` + 编写BaseDao + 导入sql操作工具包 ```xml commons-dbutils commons-dbutils 1.7 ``` + 创建BaseDao.java类 ```java package com.dream.dao.impl; import com.dream.utils.JdbcUtils; import org.apache.commons.dbutils.QueryRunner; import org.apache.commons.dbutils.handlers.BeanHandler; import org.apache.commons.dbutils.handlers.BeanListHandler; import org.apache.commons.dbutils.handlers.ScalarHandler; import java.sql.Connection; import java.sql.SQLException; import java.util.List; /** * @author 匠人码农 * @date 2020/11/11 10:55 * 概要: * BaseDao类 * */ public abstract class BaseDao { //使用apache的DbUtils操作数据库 private final QueryRunner queryRunner = new QueryRunner(); /** * 用来执行insert,update,delete语句 * @param sql 要执行的sql文 * @param args sql的参数 * @return 如果查询到结果返回>1的值.
* 返回-1 表示没有查询到结果。 */ public int update(String sql,Object ... args){ //获取连接 Connection conn = JdbcUtils.getConnection(); //执行sql try { return queryRunner.update(conn,sql,args); } catch (SQLException e) { e.printStackTrace(); } finally { //关闭连接 JdbcUtils.close(conn); } return -1; } /** * 查询返回一个JavaBean的sql * @param type JavaBean类型 * @param sql 执行的sql文 * @param args sql的参数 * @param 类型的泛型 * @return 返回一个T类型的对象 */ public T queryForOne(Class type,String sql,Object ... args){ //获取连接 Connection conn = JdbcUtils.getConnection(); //执行sql try { return queryRunner.query(conn,sql,new BeanHandler(type),args); } catch (SQLException e) { e.printStackTrace(); } finally { //关闭连接 JdbcUtils.close(conn); } return null; } /** * 返回一个多个JavaBean的List结果集合 * @param type JavaBean类型 * @param sql 执行的sql文 * @param args sql的参数 * @param 类型的泛型 * @return 返回一个List */ public List queryForList(Class type,String sql,Object ... args){ //获取连接 Connection conn = JdbcUtils.getConnection(); //执行sql try { return queryRunner.query(conn,sql,new BeanListHandler(type),args); } catch (SQLException e) { e.printStackTrace(); } finally { //关闭连接 JdbcUtils.close(conn); } return null; } /** * 返回一个一行一列的结果 * @param sql 查询的sql * @param args sql参数 * @return 返回一个一行一列的结果 */ public Object queryForSingleValue(String sql,Object ... args){ //获取连接 Connection conn = JdbcUtils.getConnection(); //执行sql try { return queryRunner.query(conn,sql,new ScalarHandler(),args); } catch (Exception e) { e.printStackTrace(); }finally { JdbcUtils.close(conn); } return null; } } ``` + 编写UserDao和测试 + 创建UserDao接口类 ```java package com.dream.dao; import com.dream.bean.User; /** * @author 匠人码农 * @date 2020/11/11 13:34 * 概要: * UserDao接口类 */ public interface UserDao { /** * 根据用户名查询用户信息 * @param userName 用户名 * @return 如果为null则用户不存在,否则结果为查询的用户信息。 */ User queryUserByUserName(String userName); /** * 根据用户名和密码查询用户信息 * @param userName 用户名 * @param password 密码 * @return 如果为null则用户名不存在或者密码错误,否则该用户存在。 */ User queryUserByUserNameAndPassword(String userName,String password); /** * 保存用户信息 * @param user 用户信息 * @return 如果为-1,保存失败。否则保存成功。 */ int saveUser(User user); } ``` + 创建USerDao接口实现类 ```java package com.dream.dao.impl; import com.dream.bean.User; import com.dream.dao.UserDao; /** * @author 匠人码农 * @date 2020/11/11 13:43 * 概要: * UserDao接口的实现类 */ public class UserDaoImpl extends BaseDao implements UserDao { @Override public User queryUserByUserName(String userName) { String sql = "select `id`,`username`,`password`,`email` from t_user where username = ?"; return queryForOne(User.class,sql,userName); } @Override public User queryUserByUserNameAndPassword(String userName, String password) { String sql = "select `id`,`username`,`password`,`email` from t_user where username = ? and password = ?"; return queryForOne(User.class,sql,userName,password); } @Override public int saveUser(User user) { String sql = "insert into t_user(`username`,`password`,`email`,`registtime`) values(?,?,?,?)"; return update(sql,user.getUserName(),user.getPassword(),user.getEmail(),user.getRegistTime()); } } ``` + UserDao测试类 ```java package com.dream.dao; import com.dream.bean.User; import com.dream.dao.impl.UserDaoImpl; import org.junit.Test; import java.text.SimpleDateFormat; import java.util.Date; /** * @author 匠人码农 * @date 2020/11/11 14:08 * 概要: * UserDao测试类 */ public class UserDaoTest { @Test public void queryUserByUserName() { UserDao userDao = new UserDaoImpl(); if(userDao.queryUserByUserName("admin") != null){ System.out.println("admin用户存在!"); }else{ System.out.println("admin用户不存在!"); } if(userDao.queryUserByUserName("gust") != null){ System.out.println("gust用户存在!"); }else{ System.out.println("gust用户不存在!"); } } @Test public void queryUserByUserNameAndPassword() { UserDao userDao = new UserDaoImpl(); if(userDao.queryUserByUserNameAndPassword("admin","admin") != null){ System.out.println("登录成功"); }else{ System.out.println("用户名或者密码错误!"); } if(userDao.queryUserByUserNameAndPassword("admin","123456") != null){ System.out.println("登录成功"); }else{ System.out.println("用户名或者密码错误!"); } } @Test public void saveUser() { User user = new User(); user.setUserName("gust"); user.setPassword("gust"); user.setEmail("gust@test.com"); Date date = new Date(); SimpleDateFormat dateFormat= new SimpleDateFormat("yyyy/MM/dd hh:mm:ss"); user.setRegistTime(dateFormat.format(date.getTime())); UserDao userDao = new UserDaoImpl(); if(userDao.queryUserByUserName(user.getUserName()) != null){ System.out.println("用户已经被注册,请更换用户名!"); }else { if (-1 != userDao.saveUser(user)) { System.out.println("用户注册成功!"); } else { System.out.println("用户注册失败!"); } } } } ``` + UserService层 + UserService接口 ```java package com.dream.service; import com.dream.bean.User; /** * @author 匠人码农 * @date 2020/11/11 16:27 * 概要: * 用户接口 */ public interface UserService { /** * 注册用户功能 * @param user */ void registUser(User user); /** * 登录 * @param user * @return */ User login(User user); /** * 检查用户是否已经存在 * @param userName * @return true:表示用户已经存在
* false:表示用户不存在 * */ boolean existsUserName(String userName); } ``` + UserService接口实现类 ```java package com.dream.service.impl; import com.dream.bean.User; import com.dream.dao.UserDao; import com.dream.dao.impl.UserDaoImpl; import com.dream.service.UserService; /** * @author 匠人码农 * @date 2020/11/11 16:33 * 概要: * 业务处理service */ public class UserServiceImpl implements UserService { //UserDao UserDao userDao = new UserDaoImpl(); @Override public void registUser(User user) { userDao.saveUser(user); } @Override public User login(User user) { return userDao.queryUserByUserNameAndPassword(user.getUserName(),user.getPassword()); } @Override public boolean existsUserName(String userName) { if(null != userDao.queryUserByUserName(userName)){ return true; } return false; } } ``` + 实现注册功能 + 创建RegsitServlet.java ```java package com.dream.servlet; import com.dream.bean.User; import com.dream.service.UserService; import com.dream.service.impl.UserServiceImpl; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; /** * @author 匠人码农 * @date 2020/11/11 17:11 * 概要: * 注册用户功能 */ public class RegistServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { User user = new User(); UserService userService = new UserServiceImpl(); //获取请求参数 //用户名 String userName = req.getParameter("username"); //密码 String password = req.getParameter("password"); //邮件地址 String email = req.getParameter("email"); //验证码 String code = req.getParameter("code"); //检查验证码 if(!"6n6np".equals(code)){ //跳转回登录界面 req.getRequestDispatcher("/pages/user/regist.html").forward(req,resp); } else { //bean设定 user.setUserName(userName); user.setPassword(password); user.setEmail(password); user.setRegistTime(new SimpleDateFormat("yyyy/MM/dd hh:mm:ss").format(new Date())); //检查用户名是否存在 //已经存在 if(userService.existsUserName(userName)){ //跳转回登录页面 req.getRequestDispatcher("/pages/user/regist.html").forward(req,resp); //不存在 } else { //跳转到成功页面 userService.registUser(user); req.getRequestDispatcher("/pages/user/regist_success.html").forward(req,resp); } } } } ``` + 在Web.xml中配置RegistServlet ```xml RegistServlet com.dream.servlet.RegistServlet RegistServlet /registServlet ``` + 修改regist.html的表单action和method方法 ```html
``` + 在regsit.html,regsit_success.html中添加base标签 ```html ``` + 编写LoginServlet实现登录功能 + 创建LoginServlet.java ```java package com.dream.servlet; import com.dream.bean.User; import com.dream.service.UserService; import com.dream.service.impl.UserServiceImpl; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author 匠人码农 * @date 2020/11/11 20:34 * 概要: * 登录功能实现 */ public class LoginServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //获取请求参数 String userName = req.getParameter("username"); String password = req.getParameter("password"); //调用业务逻辑 User user = new User(); user.setUserName(userName); user.setPassword(password); //创建service对象 UserService userService = new UserServiceImpl(); //用户和密码正确 if(null != userService.login(user)){ //跳转到登录成功页面 req.getRequestDispatcher("/pages/user/login_success.html").forward(req,resp); }else{ //跳转到登录页面 req.getRequestDispatcher("pages/user/login.html").forward(req,resp); } } } ``` + 在Web.xml中配置LoginServet ```xml LoginServlet com.dream.servlet.LoginServlet LoginServlet /loginServlet ``` + 修改login.html中表单中的action和method方法。 ```html ``` + 在login.html和login_success.html中添加base标签 ```html ``` # 版本迭代(第二版) ## 页面jsp动态化 + 把所有的html文件修改成jsp文件 + 在修改后的jsp页面中添加page指令 + 把相应的地方进行修改(command+shift+r 替换) + 修改后的页面内容,登录,注册功能再次确认。 ## 抽取页面中相同的内容 ## 把base改成动态获取 ## 添加注册等登录错误的时候回显错误信息 + 登录的错误信息以及回显内容 当输入用户名和密码后,错误不能正常登录时,如果用户名不为null,则那么回显用户名。 错误提示信息。 + 注册的错误信息以及回显内容 当注册用户存在的时候,显示"用户已经存在"并把用户名和邮件地址回显。 当验证码错误的时候,显示"验证码错误"并把用户名和邮件地址回显。 ## 优化Servlet 通常的开发中不会以一个功能一个servlet。而是以一个模块一个servlet。比如用户模块包括登录,注册等。 此时会把登录和注册合并为一个servlet然后再servlet中记性分发处理。 ## 对用户模块的LoginServlet和RegistServlet进行合并生成UserServlet 废弃LoginServlet类和RegistServlet类 在UserServlet进行业务分离 把LoginServlet和RegistServlet类的实现方法复制粘贴到UserServlet类,修改方法名称和画面的隐藏元素action的值一样即可。 ## 使用反射优化业务的分发 ## 把多个模块都是用的部分进行BaseServlet提取 ## 使用beanUtils进行请求参数的封装,注入到JavaBean中。 + 追加pom依赖 ```xml commons-beanutils commons-beanutils 1.9.4 ``` + 使用beanutils进行javabean的封装 ```java //使用beanUtils进行JavaBean的封装 try { BeanUtils.populate(user,req.getParameterMap()); System.out.println("BeanUtils封装结果:" + user); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } ``` + 提取WebUtils工具类 把JavaBean的封装过程放到工具类中。 + BeanUtils的核心是通过Bean的setter方法把parametor的值注入。 + 使用注意事项 1. 画面的name属性必须和Bean的成员名一致。否则那个字段的Bean封装就会失败,值为null。 2. 当画面的属性个数和bean的成员个数不一致的时候。画面属性没有的需要单独进行设定。 ## 把jsp文件的表达式修改成EL表达式。 # 图书管理模块 ## 创建表结构并插入数据 ## 创建Bean ## 创建Dao和测试类 ## 创建Service和测试类 ## 创建Web层,联通测试 ### 实现显示图书列表功能 ### 实现添加图书功能 + 表单重复提交 当用户提交完请求,浏览器会记录下最后一次请求的所有信息。当用户再次刷新(F5)页面时候,会再一次提交形式。 ### 实现修改图书功能 + 修改图书的部分分为两块 1. 把要修改的图书信息回显到表单列表 2. 提交图书信息进行Db的保存 + 由于图书添加功能和修改图书功能同时使用book_edit.jsp,所以提交按钮做了两件事件。一个是添加,一个是修改。 那么问题就出现了。如何判断操作是添加还是修改? + 解决方案1 在修改请求之前把,附带一个当前做的操作,并这测到隐藏域中。 + 解决方案2 由于更新需要图书的ID,所以判断有无请求参数id,有的话则为更新,无则为添加。 + 解决方案3 由于更新操作画面就要有要修改图书的信息即图书对象book,所以判断有无book对象,有则为更新,无则为添加。 ### 把图书列表功能追加改页的功能。 在事假开发中由于数据加载影响性能,所以每一页只显示一部分数据。提高性能。 1. 对图书分页进行分析,确立分页模型。 + 当前页码 pageNo 客户端记性传递进行确定 + 总页码数 pageTotal = recordTotal/ pageSize 取天棚 + 总记录数 recordTotal 通过sql获取(select count(1) from t_book where delflg = '0') + 每页可容纳记录数 pageSize 可以通过客户端传递进行设定 也可以由页面布局决定 + 当前页记录数据 items 由sql语句取得 select * from t_book limit begin,pageSize 上面sql由begin决定。 begin = (pageNo -1) * pageSize 分页功能分析如下 ![page_analyse](https://gitee.com/fowner/imge/raw/master/page_analyse.png) 2. 创建分页模型 Page.java类 五个属性 pageNo 当前页码 pageTotal 总页码数 recordTotal 总记录数 pageSize 每页容纳件数 items 当前页数据 3. 基本实现分页(数据的回传) 4. 首页,上一页,下一页,末页。实现 5. 分页模块中跳转到指定页和指定每页容纳件数 6. 分页模块中页码1 2 【3】 4 5 的显示要连续显示五页,并且可以跳转 + 情况1 总页码的数量小于等于5 页码的范围是:1~5 1页: 1 2页: 1 2 3页: 1 2 3 4页: 1 2 3 4 5页: 1 2 3 4 5 + 情况2 页码的总数量大于5 假设一共10页 页码的范围是1~5 小情况1:当前页码为前3页的 1 2 3 【1】 2 3 4 5 1 【2】 3 4 5 1 2 【3】 4 5 小情况2:当前页码为最后3页 页码范围:总页码-4~总页码 6 7 【8】 9 10 6 7 8 【9】 10 6 7 8 9 【10】 小情况3:小情况1和2以外的 页码范围:当前页码-2~ 当前页码+2 2 3 【4】 5 6 3 4 【5】 6 7 4 5 【6】 7 8 7. 分页功能按照6的思路实现后,修改图书管理页面的【修改】,【删除】,【追加】功能。 # 实现首页的分页功能 难点是: 项目启动的初始化页面的URL或者默认路径是工程路径,要实现分页的功能。就得需要pageNo和pageSize。 解决方案如下图所示 ![index_page_firset_request_param_analyse](https://gitee.com/fowner/imge/raw/master/index_page_firset_request_param_analyse.png) 9.实现首页的价格检索功能 ![index_price_from_to_analyse](https://gitee.com/fowner/imge/raw/master/index_price_from_to_analyse.png) # 登录-显示用户名 + 在servlet中把登录成功信息存放到session中 + 在登录成功页面获取session的用户信息 # 注销-注销用户 + 在servet中销户session + 重定向到首页或登录页面 # 表单重复提交 ## 表单重复提交3种情况 1. 提交表单。服务器使用请求转发来进行也买跳转。设个时候用户刷新操作。就会发起最后一次请求。造成表单重复提交。 解决方案:使用重定向来进行转发 2. 用户正常提交服务器,由于网络原因。客户重复点击按钮,进行提交。也造成重复提交。 3. 用户正常提交表单,然后回退,继续点击提交按钮。也会造成表单重复提交。 ## 验证码底层原理(解决了表单重复提交的问题) ![code_resubmit_analyse](https://gitee.com/fowner/imge/raw/master/code_resubmit_analyse.png) ## 谷歌验证码的使用 1.导入谷歌验证码依赖kaptcha 2. 在web.xml中配置用户生成验证码的servlet url-pattern注意写成.jpg 3. 在表单的中使用img标签导入生成的验证码路径 4. 在服务器获取谷歌生成的验证码和客户端发来的请求数据 5. 在servlet中进行逻辑的书写 1> 获取存放在session中的谷歌验证码 2> 获取用户输入的验证码 3> 判断验证码的正确性进行相应的逻辑处理。 ## 实现单击验证码后更新验证码 给验证码的图片注册单击事件 事件中给src属性赋值即可。 注意事项 ```s 当完成上面的内容后,发现谷歌浏览器和safari浏览器点击没有问题而,IE和火狐浏览器都不能通过单机事件改变验证码。 原因在于 火狐浏览器和IE浏览器对请求的内容进行了缓存。 我们需要追加一个时间戳来让每次请求的内容变得不一样。这样浏览器就娶不到缓存的值了。 ``` # 购物车模块 ## 购物车实现技术版本 1. session版本 2. 购物出信息保存到数据库 3. redis(缓存) + 数据库 + Cookie ``` 这次我们采用的是session版本 由于是把购物车信息保存到session,而没有保存到数据库,所以没有Dao层。 又由于session是web层的api所以用不到service层。也就没有service层。 ``` ## 购物车模型分析 ![cart_analyse](https://gitee.com/fowner/imge/raw/master/cart_analyse.png) ### 购物车商品信息(CartItem) + 商品Id goodId + 商品名称 goodName + 数量 count + 单价 price + 金额 totalPrice ### 购物车信息(Cart) + 商品总件数 totalCount + 商品总金额 totalPrice + 购物车商品 items ### 购物车功能(Cart的方法) + 加入购物车 + 删除商品 + 修改商品数量 + 清空购物车 ## 购物车实现 ### 定义好Cart类 ### 定义好CartItem类 ### 定义Servlet类 ### ### 修改购物车商品数量 ### 首页购物车信息回显 # 订单模块 订单模块分析 ![order_analyse](https://gitee.com/fowner/imge/raw/master/order_analyse.png) ## 订单模块的去支付功能完成 ### 购物车保留的内容应该是没有支付的商品(自己追加) # 使用Filter实现管理员的拦截 ================================= 2021/12/23 更新 ================================= > 从首页 -》 后台管理 -》 管理员登录check > > 实现功能 > 从图书管理进入check管理员是否登录 > 未登录的状态 admin——login画面 > 登录之后的页面跳转有待修改,应该跳转到图书管理页面 > > # 使用ThreadLocal实现 ## ThreadLocal的使用 ### 作用 解决多线程的数据安全问题 ThreadLocal可以为当前线程关联一个数据(普通变量,对象,数组,集合均可) ThreadLocal的特点 1.Thread可以为当前线程关联一个数据(可以像猫一样存取数据,key是当前线程) 2.只可以保存一个数据。(如果想保存多个需要创建多个线程。) 3.ThreadLocal类的实例必须是静态对象 4.ThreadLocal类中存储的数据,在线程结束后,由JVM自动回收。 ## 使用ThreadLocal和Filter来实现事务管理 ![ThreadLocal_transaction](https://gitee.com/fowner/imge/raw/master/ThreadLocal_transaction.png) 注意点 ``` 对于事务的处理: 1.不能每执行一次数据库操作就关闭连接,而是当一个业务处理完毕之后进行数据库连接的一次性关闭。 2.对于Dao层异常,不仅要对dao层进行捕获而且还得往外抛,当进行事务提交回滚的时候进行捕获,否则事务提交或者回滚的时候就不能捕获到异常。 3.事务的提交在servlet进行。 由于每一个servelt都得进行事务的处理,所以都需要尽心代码的修改。所以此时需要使用Filter来进行事务的操作。 当servlet执行完毕后,执行Filter处理进行事务的提交和回滚操作。 ``` 使用Filter实现事务管理 ![Fileter_transaction](https://gitee.com/fowner/imge/raw/master/Fileter_transaction.png) + 内容优化(IDEA有料版自动check) web.xml文件配置内容顺序如下 icon display-name description distributable context-param filter filter-mapping listener servlet servlet-mapping session-config mime-mapping welcome-file-list error-page taglib resource-env-ref resource-ref security-constraint login-config security-role env-entry ejb-ref ejb-local-ref ????? 待支付 -> 待发货 -> 待签收 -> 已签收 等待解决的问题。 考虑的事情 支付功能 取消订单 退货 # 2022/03/21更新 # 问题调查解决 ## 1. 前提 首先确保执行了下面的SQL语句,并正确插入到了数据库。 ```sql --t_permission insert into t_permission (permissionid,permissionname,permissionremarker,createdatetime) values('R001','管理员管理','系统管理员添加管理员',current_timestamp); insert into t_permission (permissionid,permissionname,permissionremarker,createdatetime) values('M001','图书管理','查看,添加,删除,更新图书信息,',current_timestamp); insert into t_permission (permissionid,permissionname,permissionremarker,createdatetime) values('M002','订单管理','查看,更新订单信息',current_timestamp); --t_role insert into t_role (roleid,rolename,roleremarker,createdatetime) values('00','系统管理员','用于管理整个系统,拥有所有权限',current_timestamp); insert into t_role (roleid,rolename,roleremarker,createdatetime) values('01','管理员','管理图书信息,订单信息等',current_timestamp); insert into t_role (roleid,rolename,roleremarker,createdatetime) values('02','普通会员','会员',current_timestamp); --root超级管理员必须手动插入,然后通过画面追加普通管理员。 --t_user_role insert into t_user_role(username,roleid,createdatetime) values('root','00',current_timestamp); --t_role_permission insert into t_role_permission (roleid,permissionid,createdatetime) values ('00','R001',current_timestamp); insert into t_role_permission (roleid,permissionid,createdatetime) values ('01','M001',current_timestamp); insert into t_role_permission (roleid,permissionid,createdatetime) values ('01','M002',current_timestamp); ``` ## 2. 错误信息 奇怪的是:Mac端直接从gitee克隆后运行时,注册,登录功能都没有问题。 而从win10端直接从gitee克隆后运行时,注册时就报下面的错误。 ``` java.lang.reflect.InvocationTargetException at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.dream.servlet.BaseServlet.doPost(BaseServlet.java:51) at javax.servlet.http.HttpServlet.service(HttpServlet.java:681) at javax.servlet.http.HttpServlet.service(HttpServlet.java:764) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at com.dream.filter.TransactionFilter.doFilter(TransactionFilter.java:22) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:543) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81) at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:698) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:366) at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:639) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:881) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1647) at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:748) Caused by: java.lang.NullPointerException at com.dream.service.impl.UserServiceImpl.registUser(UserServiceImpl.java:51) at com.dream.servlet.UserServlet.regist(UserServlet.java:179) ... 32 more java.lang.RuntimeException: java.lang.reflect.InvocationTargetException at com.dream.servlet.BaseServlet.doPost(BaseServlet.java:56) at javax.servlet.http.HttpServlet.service(HttpServlet.java:681) at javax.servlet.http.HttpServlet.service(HttpServlet.java:764) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at com.dream.filter.TransactionFilter.doFilter(TransactionFilter.java:22) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:543) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81) at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:698) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:366) at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:639) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:881) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1647) at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:748) Caused by: java.lang.reflect.InvocationTargetException at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.dream.servlet.BaseServlet.doPost(BaseServlet.java:51) ... 27 more Caused by: java.lang.NullPointerException at com.dream.service.impl.UserServiceImpl.registUser(UserServiceImpl.java:51) at com.dream.servlet.UserServlet.regist(UserServlet.java:179) ... 32 more ``` ## 3. 问题分析 代码是一样的,但是DB使用的是不同的。 猜测是DB的问题。 于是通过macos端连接windos端的mysql,运行结果也出了同样的错误。 猜测可能是DB字符集设置的问题,通常字符集出的问题比较多。 通过下面命令查看字符集后,发现有值为latin1的设定。由此确定就是DB的字符集的问题。 + 查看当前mysql的字符集 ``` mysql> show variables like '%char%'; +--------------------------+---------------------------------------------------------+ | Variable_name | Value | +--------------------------+---------------------------------------------------------+ | character_set_client | utf8 | | character_set_connection | utf8 | | character_set_database | latin1 | | character_set_filesystem | binary | | character_set_results | utf8 | | character_set_server | latin1 | | character_set_system | utf8 | | character_sets_dir | C:\Program Files\MySQL\MySQL Server 5.7\share\charsets\ | +--------------------------+---------------------------------------------------------+ 8 rows in set, 1 warning (0.00 sec) mysql> ``` ## 4. 解决方案 下面两种解决方案都试过了,都没有问题。 ### 方案1 在mysql请求url中添加:useUnicode=true&characterEncoding=utf8 最后jdbc.properties文件的url的值改为: jdbc:mysql://localhost:3306/BookShop?useSSL=false&useUnicode=true&characterEncoding=utf8 ### 方案2 修改mysql的字符集,然后重启mysql的服务即可。 我用的是5.7版本的mysql,把5.7改成自己的版本号即可。 把C:\ProgramData\MySQL\MySQL Server 5.7\my.ini 注意:ProgramData文件夹默认是隐藏的。 ```properties default-character-set=utf8 character-set-server=utf8 ``` # 2022-04-16更新 添加表结构定义文档和ER图。 请参照文件 [TablesDefine&ER.xlsx](TablesDefine&ER.xlsx)