附上github源码:https://github.com/fukaidi39/bookstore
能点个star就再好不过了
项目说明:
-
基于JavaWeb图书管理系统
-
来源:尚硅谷JavaWeb课程书城项目,主要是为了巩固JavaWeb的相关基础知识
-
涉及技术:html,css,js,jquery,servlet,xml,jsp,Ajax,cookie,session
-
实现功能:
-
1、用户注册、登录模块。(引入谷歌验证码及错误提示)
-
2、实现图书模块,包括增删查改图书以及图书分页。
-
3、实现购物车模块,包括加入购物车、删除商品项、清空购物车、修改商品数量等。
-
4、实现订单模块,包括结账生成订单、管理员发货、查询订单及详情、用户签收订单等。
-
5、使用 Filter 和 ThreadLocal 来组合管理事务
-
6、Filter实现权限管理,管理员可以访问后台,进行图书管理和订单管理。
-
项目阶段一:
表单验证
验证用户名: 必须由字母, 数字下划线组成, 并且长度为 5 到 12 位
验证密码: 必须由字母, 数字下划线组成, 并且长度为 5 到 12 位
验证确认密码: 和密码相同
邮箱验证: xxxxx@xxx.com
验证码: 现在只需要验证用户已输入。 因为还没讲到服务器。 验证码生成。
<head>
<meta charset="UTF-8">
<title>尚硅谷会员注册页面</title>
<link type="text/css" rel="stylesheet" href="static/css/style.css" >
<style type="text/css">
.login_form{
height:420px;
margin-top: 25px;
}
</style>
<script type="text/javascript" src = "script/jquery-1.7.2.js"></script>
<script type="text/javascript">
$(function (){
//注册按钮绑定单击事件
$("#sub_btn").click(function (){
//验证用户名: 必须由字母, 数字下划线组成, 并且长度为 5 到 12 位
var regex = /^[a-z0-9_-]{5,12}$/;
var username = $("#username").val();
var password = $("#password").val();
var repwd = $("#repwd").val();
var email = $("#email").val();
if(!regex.test(username)){
$("span.errorMsg").text("输入用户名不合法!");
return false;
}
//验证密码: 必须由字母, 数字下划线组成, 并且长度为 5 到 12 位
if(!regex.test(password)){
$("span.errorMsg").text("输入密码不合法!");
return false;
}
//验证确认密码: 和密码相同
if(repwd != password){
$("span.errorMsg").text("前后密码不一致!");
return false;
}
//邮箱验证: xxxxx@xxx.com
var emailRegex = /^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/;
if(!emailRegex.test(email)){
$("span.errorMsg").text("输入电子邮件不合法!")
return false;
}
//验证码: 现在只需要验证用户已输入。 因为还没讲到服务器。 验证码生成
var codeText = $("#code").val();
//去除前后重复 $.trim()方法去除前后空格
codeText = $.trim(codeText);
if(codeText == null || codeText == ""){
$("span.errorMsg").text("请输入正确的验证码!")
return false;
}
//警告框清空
$("span.errorMsg").text("")
});
});
</script>
</head>
项目阶段二:用户模块
用户注册和登陆的实现。
需求 1: 用户注册
需求如下:
1) 访问注册页面
2) 填写注册信息, 提交给服务器
3) 服务器应该保存用户
4) 当用户已经存在----提示用户注册 失败, 用户名已存在
5) 当用户不存在-----注册成功
需求 2: 用户登陆
需求如下:
1) 访问登陆页面
2) 填写用户名密码后提交
3) 服务器判断用户是否存在
4) 如果登陆失败 —>>>> 返回用户名或者密码错误信息
5) 如果登录成功 —>>>> 返回登陆成功 信息
- JavaEE项目的三层架构:
分层的目的是为了解耦。 解耦就是为了降低代码的耦合度。 方便项目后期的维护和升级。
1.搭建项目环境
- 新建Javaweb模块,导入Tomcat依赖和Framework support
- 建立静态页面复制到web目录下
- 在WEB-INF下新建lib导入需要用到的jar包
2. 创建数据库对应的表和JavaBean对象
drop database if exists book;
create database book;
use book;
create table t_user(
`id` int primary key auto_increment,
`username` varchar(20) not null unique,
`password` varchar(32) not null,
`email` varchar(200)
);
insert into t_user(`username`,`password`,`email`)values('admin','admin','admin@atguigu.com');
select * from t_user;
3.编写工具类JdbcUtils
package com.zju.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 godfu
* @Date:2022/3/10-16:15
*/
public class JdbcUtils {
private static DruidDataSource dataSource;
//静态代码初始化读取资源,获取数据库连接池的操作
static {
try {
Properties properties = new Properties();
//读取jdbc属性文件到输入流
InputStream is = JdbcUtils.class.getClassLoader().getResourceAsStream("com/zju/resources/jdbc.properties");
//从流中加载数据
properties.load(is);
//创建数据库连接池
dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取数据库的连接
* @return 如果返回Null则说明连接失败,否则获取连接成功
*/
public static Connection getConnection(){
Connection conn = null;
try {
conn = dataSource.getConnection();
} catch (Exception e) {
e.printStackTrace();
}
return conn;
}
/**
* 关闭连接,将资源放回数据库连接池
*/
public static void close(Connection conn){
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
4.编写BaseDao
- 导入DbUtils的jar包
package com.zju.dao;
import com.zju.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 godfu
* @Date:2022/3/10-18:59
*/
public abstract class BaseDao {
//使用Dbutils操作数据库crud
private QueryRunner queryRunner = new QueryRunner();
/**
* 执行增删改操作
* @param sql:执行的sql语句
* @param args:传入占位符中的参数
* @return 返回-1表示执行失败,其他表示影响的函数
*/
public int update(String sql, Object... args){
Connection conn = JdbcUtils.getConnection();
try {
return queryRunner.update(conn,sql,args);
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.close(conn);
}
return -1;
}
/**
* 查询返回一个javaBean对象的sql语句
* @param sql:执行的sql语句
* @param type:返回的对象类型
* @param args:sql语句对应的参数
* @param <T>:返回类型的泛型
* @return
*/
public <T> T queryForOne(String sql, Class<T> type, Object ... args){
Connection conn = JdbcUtils.getConnection();
try {
return queryRunner.query(conn,sql, new BeanHandler<T>(type), args);
} catch (Exception e) {
e.printStackTrace();
} finally {
JdbcUtils.close(conn);
}
return null;
}
/**
* 查询返回多个javaBean对象的sql语句
* @param sql:执行的sql语句
* @param type:返回对象的类型
* @param args: sql语句对应的参数
* @param <T>:返回类型的泛型
* @return
*/
public <T>List<T> queryForList(String sql, Class<T> type, Object ... args){
Connection conn = JdbcUtils.getConnection();
try {
return queryRunner.query(conn, sql, new BeanListHandler<T>(type),args);
} catch (Exception 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();
try {
return queryRunner.query(conn,sql, new ScalarHandler(),args);
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.close(conn);
}
return null;
}
}
5.编写UserDao和测试
package com.zju.dao;
import com.zju.pojo.User;
/**
* @author godfu
* @Date:2022/3/10-19:56
*/
public interface UserDao {
/**
* 根据用户名查询用户信息(注册)
* @param username 用户名
* @return 查询到的用户对象,如果没有返回null
*/
public User queryUserByUsername(String username);
/**
* 根据用户名和密码查询用户信息(登录)
* @param username 用户名
* @param password 密码
* @return 返回null说明用户名或者密码错误
*/
public User queryUserByUsernameAndPassword(String username, String password);
/**
* 保存用户信息
* @param user 用户对象
* @return 返回1说明保存成功,返回-1表示操作失败
*/
public int saveUser(User user);
}
UserDao实现类:
package com.zju.dao.impl;
import com.zju.dao.BaseDao;
import com.zju.dao.UserDao;
import com.zju.pojo.User;
/**
* @author godfu
* @Date:2022/3/10-20:06
*/
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(sql,User.class,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(sql,User.class,username,password);
}
@Override
public int saveUser(User user) {
String sql = "insert into t_user(`username`,`password`,`email`) values(?,?,?)";
return update(sql, user.getUsername(), user.getPassword(), user.getEmail());
}
}
- 在类中按下ctrl+shift+T快速创建测试类
package com.zju.test;
import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;
import com.zju.dao.UserDao;
import com.zju.dao.impl.UserDaoImpl;
import com.zju.pojo.User;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* @author godfu
* @Date:2022/3/10-20:55
*/
public class UserDaoTest {
@Test
public void queryUserByUsername() {
UserDao userDao = new UserDaoImpl();
System.out.println(userDao.queryUserByUsername("admin"));
}
@Test
public void queryUserByUsernameAndPassword() {
UserDao userDao = new UserDaoImpl();
if(userDao.queryUserByUsernameAndPassword("admin","admin") == null){
System.out.println("登录失败,用户名或密码错误");
}else{
System.out.println("登录成功");
}
}
@Test
public void saveUser() {
UserDao userDao = new UserDaoImpl();
System.out.println(userDao.saveUser(new User(null ,"fkd", "123456", "1138@qq.com")));
}
}
6. 编写UserService和测试
package com.zju.service;
import com.zju.pojo.User;
/**
* @author godfu
* @Date:2022/3/10-21:35
*/
public interface UserService {
/**
* 用户注册业务
* @param user
* @return 返回影响行数,-1时表示注册失败
*/
public int registerUser(User user);
/**
* 用户登录业务
* @param username 用户名
* @param password 密码
* @return 当返回null时登录失败
*/
public User login(String username, String password);
/**
* 检查用户名是否存在
* @param username
* @return 返回true时表示用户名已经存在
*/
public boolean existsUser(String username);
}
package com.zju.service.impl;
import com.zju.dao.UserDao;
import com.zju.dao.impl.UserDaoImpl;
import com.zju.pojo.User;
import com.zju.service.UserService;
/**
* @author godfu
* @Date:2022/3/10-21:52
*/
public class UserServiceImpl implements UserService{
private UserDao userDao = new UserDaoImpl();
@Override
public int registerUser(User user) {
return userDao.saveUser(user);
}
@Override
public User login(String username, String password) {
return userDao.queryUserByUsernameAndPassword(username, password);
}
@Override
public boolean existsUser(String username) {
if(userDao.queryUserByUsername(username) == null){
return false;
}
return true;
}
}
7.编写web层
1.实现用户注册的功能:
-
配置好web.xml与Tomcat服务器
-
修改静态页面regist.html与regist_success静态页面
package com.zju.web; /**
* @author godfu
* @Date:2022/3/10-22:41
*/
import com.zju.pojo.User;
import com.zju.service.UserService;
import com.zju.service.impl.UserServiceImpl;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;
public class RegistServlet extends HttpServlet {
private UserService userService = new UserServiceImpl();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//会暴露密码
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取请求的参数信息
String username = request.getParameter("username");
String password = request.getParameter("password");
String email = request.getParameter("email");
String code = request.getParameter("code");
//判断验证码是否正确
if(code.equalsIgnoreCase("6n6np")){
//判断用户名是否存在
if(userService.existsUser(username)){
//用户存在
request.getRequestDispatcher("/pages/user/regist.html").forward(request,response);;
System.out.println("用户名已经存在!");
}else{
//用户成功注册
userService.registerUser(new User(null, username,password,email));
request.getRequestDispatcher("/pages/user/regist_success.html").forward(request,response);
}
}else{
//验证码错误
request.getRequestDispatcher("/pages/user/regist.html").forward(request,response);
System.out.println("验证码错误!");
}
}
}
2.实现用户登录功能:
分析代码逻辑:
-
配置好web.xml
-
修改静态页面login.html与login_success静态页面
package com.zju.web; /**
* @author godfu
* @Date:2022/3/11-11:15
*/
import com.zju.service.UserService;
import com.zju.service.impl.UserServiceImpl;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;
public class LoginServlet extends HttpServlet {
private UserService userService = new UserServiceImpl();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//暴露密码,故不用
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取请求参数
String username = request.getParameter("username");
String password = request.getParameter("password");
//调用UserService.login()方法判断是否登录成功
if(userService.login(username,password) == null){
request.getRequestDispatcher("/pages/user/login.html").forward(request,response);
}else{
request.getRequestDispatcher("/pages/user/login_success.html").forward(request,response);
}
}
}
IDEA中Debug调试:
1.Debug调试代码,首先需要两个元素:断点+Debug启动服务器
2.测试工具栏:
从左到右依次:
- 让代码往下执行一行
- 可以进入当前方法体内(自己写的代码,非框架源码)
- 强制进入当前方法体内
- 跳出当前方法体外
- 停在光标所在行(相当于临时断点)
3.变量窗口
变量窗口:它可以查看当前方法范围内所有的有效的变量
4.方法调用栈窗口
1.方法调用栈可以查看当前线程有哪些方法调用信息
2.下面的调用上一行的方法
5.其他调试按钮
项目阶段三:优化代码
1.页面jsp动态化
- 在html页面顶行添加page指令
- 修改文件后缀名为.jsp
- 使用IDEA搜索替换 .html为 .jsp (快捷键:ctrl+shift+r)
2.抽取页面中相同的部分
- head 中 css、 jquery、 base 标签
- 每个页面的页脚
- 登录成功后的菜单
- manager 模块的菜单
3.动态设置base标签的路径
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
String basePath = request.getScheme() http
+ "://" http://
+ request.getServerName() http://10.116.85.40
+ ":" http://10.116.85.40:
+ request.getServerPort() http://10.116.85.40:8080
+ request.getContextPath() http://10.116.85.40:8080/book
+ "/"; http://10.116.85.40:8080/book/
%>
<base href="<%=basePath%>">
<link type="text/css" rel="stylesheet" href="static/css/style.css" >
<script type="text/javascript" src = "script/jquery-1.7.2.js"></script>
注意:设置完base标签后,所有页面的跳转都是基于base标签的地址开始,没设置时按照网址当前路径相对跳转
设置base:<base href="http://localhost:8080/book/">
之后的地址计算:
<div>
<span>欢迎<span class="um_span">老板</span>光临尚硅谷书城</span>
<a href="pages/order/order.jsp">我的订单</a>
<a href="index.jsp">注销</a>
<a href="index.jsp">返回</a>
</div>
计算得:
http://localhost:8080/book/pages/order/order.jsp
http://localhost:8080/book/index.jsp
4.登录,注册错误信息提示,及表单回显
当登录不成功时把错误信息和回显表单保存到Request域当中
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取请求参数
String username = request.getParameter("username");
String password = request.getParameter("password");
//调用UserService.login()方法判断是否登录成功
if(userService.login(username,password) == null){
//把错误信息和回显表单保存到Request域中
request.setAttribute("msg", "用户名或密码错误");
request.setAttribute("username", username);
request.getRequestDispatcher("/pages/user/login.jsp").forward(request,response);
}else{
request.getRequestDispatcher("/pages/user/login_success.jsp").forward(request,response);
}
}
}
5.BaseServlet 的抽取
获取参数是以name为标识,值是value
**代码优化一:合并 LoginServlet 和 RegistServlet 程序为 UserServlet 程序 **
**优化代码二: 使用反射优化大量 else if 代码: **
用反射调用本类UserServlet中不同功能的方法
**代码优化三: 抽取 BaseServlet 程序 **
BaseServlet:
package com.zju.web; /**
* @author godfu
* @Date:2022/3/15-21:22
*/
import com.zju.service.UserService;
import com.zju.service.impl.UserServiceImpl;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;
import java.lang.reflect.Method;
public abstract class BaseServlet extends HttpServlet {
UserService userService = new UserServiceImpl();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//暴露密码,故不用
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String action = request.getParameter("action");
try {
//利用action鉴别字符串所对应的业务方法,反射获取方法对象
Method method = this.getClass().getDeclaredMethod(action, HttpServletRequest.class, HttpServletResponse.class);
//调用目标业务方法,this是当前类对象的实例
method.invoke(this, request, response);
} catch (Exception e) {
e.printStackTrace();
}
}
}
UserServlet:
package com.zju.web; /**
* @author godfu
* @Date:2022/3/15-20:19
*/
import com.zju.pojo.User;
import com.zju.service.UserService;
import com.zju.service.impl.UserServiceImpl;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
import java.lang.reflect.Method;
@WebServlet(name = "UserServlet", value = "/UserServlet")
public class UserServlet extends BaseServlet {
/**
* 处理登录业务
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
protected void login(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取请求参数
String username = request.getParameter("username");
String password = request.getParameter("password");
//调用UserService.login()方法判断是否登录成功
if(userService.login(username,password) == null){
//把错误信息和回显表单保存到Request域中
request.setAttribute("msg", "用户名或密码错误");
request.setAttribute("username", username);
request.getRequestDispatcher("/pages/user/login.jsp").forward(request,response);
}else{
request.getRequestDispatcher("/pages/user/login_success.jsp").forward(request,response);
}
}
/**
* 处理注册业务
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
protected void regist(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取请求的参数信息
String username = request.getParameter("username");
String password = request.getParameter("password");
String email = request.getParameter("email");
String code = request.getParameter("code");
//判断验证码是否正确
if(code.equalsIgnoreCase("6n6np")){
//判断用户名是否存在
if(userService.existsUser(username)){
//用户存在
request.setAttribute("msg", "用户名已存在");
request.setAttribute("username", username);
request.setAttribute("password", password);
request.setAttribute("email", email);
request.getRequestDispatcher("/pages/user/regist.jsp").forward(request,response);;
}else{
//用户成功注册
userService.registerUser(new User(null, username,password,email));
request.getRequestDispatcher("/pages/user/regist_success.jsp").forward(request,response);
}
}else{
//验证码错误
request.setAttribute("msg", "验证码错误");
request.setAttribute("username", username);
request.setAttribute("password", password);
request.setAttribute("email", email);
request.getRequestDispatcher("/pages/user/regist.jsp").forward(request,response);
}
}
}
6.数据的封装和抽取 BeanUtils 的使用
BeanUtils 工具类, 它可以一次性的把所有请求的参数注入到 JavaBean 中。
BeanUtils 工具类, 经常用于把 Map 中的值注入到 JavaBean 中, 或者是对象属性值的拷贝操作。
1、 导入需要的 jar 包:
-
commons-beanutils-1.8.0.jar
-
commons-logging-1.1.1.jar
2、编写 WebUtils 工具类使用:
工具类要减少针对性,增加复用性
package com.zju.utils;
import org.apache.commons.beanutils.BeanUtils;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
/**
* @author godfu
* @Date:2022/3/15-22:14
*/
public class WebUtils {
/**
* 把map中的值注入到对应的javaBean属性中
* @param value 通过键值对的形式调用setXxx方法
* @param bean 实体类
* @param <T> 设置泛型
* @return 注入属性值后的实体类bean
*/
public static <T>T copyParamToBean(Map value, T bean){
try {
BeanUtils.populate(bean, value);
} catch (Exception e) {
e.printStackTrace();
}
return bean;
}
}
项目第四阶段:图书模块
1.创建数据库表和JavaBean对象
2. 编写图书模块的Dao和测试Dao
- 插入的时候是取值填充占位符,不需要取别名;查询的时候要根据别名查找bean对象对应的属性值,因此列名与属性名不一致时,需要取别名兼容
3.编写图书模块的Service和测试Service
- Service模块出curd业务外,可能还有其他业务
4.编写图书模块的Web层,和页面联调测试
- 每次显示前先查询,然后把数据放到request域中,jsp根据域中数据去显示
1.图书列表显示功能:
2.前后台的实现:
3.添加图书功能:
4.删除图书功能:
5.修改图书功能实现:
项目第五阶段:图书分页
- 分页模型Page的抽取(当前页数、总页码、总记录数、每页显示的数量、当前页数据)
- 分页的初步实现
- 首页、上一页、下一页、末页的实现
- 分页模块中跳转到指定页数功能的实现
- 分页模块中,页码5连显示并且可以跳转的实现
首页index.jsp跳转
- IDEA中的build-rebulid project可以清除缓存
分页条的抽取
- page 对象中添加 url 属性
- 在 Servlet 程序的 page 分页方法中设置 url 的分页请求地址
- 修改分页条中请求地址为 url 变量输出,并抽取一个单独的 jsp 页面
首页价格搜索
项目第六阶段:
1.登录显示用户名
2.登出注销用户
3.引入谷歌验证码
- 验证码解决表单重复提交
项目第七阶段:购物车
项目第八阶段:订单
项目第九阶段:AJAX
1.验证用户名
2.把商品添加到购物车
jar包说明:
- mysql-connector-java-5.1.7.jar:mysql厂商提供的对jdbc接口的实现类
- druid-1.1.9.jar: 数据库连接池,数据库连接操作
- commons-dbutils-1.3.jar:一个开源的JDBC工具类库,封装了针对数据库的增删改查操作
- hamcrest-core-1.3.jar和junit-4.12:测试包
- commons-beanutils-1.8.0.jar
commons-logging-1.1.1.jar BeanUtils工具类,封装实体类 - taglibs-standard-impl-1.2.1,jar 和 taglibs-sdandard-spec-1.2.1.jar JSTL标签库
- kaptcha-2.3.2.jar 谷歌验证码
- gson-2.2.4.jar: json包