谷粒书城项目
用户注册和登录(Servlet)
脉络梳理
分层的目的是为了解耦。 解耦就是为了降低代码的耦合度。 方便项目后期的维护和升级
用户注册与登录主要是锻炼Servlet技术的使用,Servlet是JavaEE 规范之一,规范就是接口。它可以接收客户端发送过来的请求, 并响应数据给客户端。接下来将对上图中的各大板块进行详细叙述。
1. 搭建环境
搭建环境这部分,我采用的是idea 2021版本的,在新建wen工程开发这部分,和以前的idea版本的创建有所区别,这部分我将用另一个博客来介绍。
2. 数据库
创建书城需要的数据库和表,采用MySQL数据库。
DROP DATABASE IF EXISTS book;
CREATE DATABASE book;
USE book;
DROP TABLE IF EXISTS t_user;
CREATE TABLE t_user(
`id` INT PRIMARY KEY AUTO_INCREMENT,
`username` VARCHAR(20) NOT NULL UNIQUE,
`password` VARCHAR(32),
`email` VARCHAR(200)
);
INSERT INTO t_user(`username`,`password`,`email`) VALUES('admin','admin','admin@qq.com');
SELECT * FROM t_user
3. Dao持久层
在此部分需要和数据库进行交互,在此之前编写JdbcUtils工具类方便操作。
JdbcUtils工具类编写
public class JdbcUtils {
private static DruidDataSource dataSource;
static {
try {
Properties properties = new Properties();
// 读取 jdbc.properties属性配置文件
InputStream inputStream = JdbcUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
// 从流中加载数据
properties.load(inputStream);
// 创建 数据库连接 池
dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);
System.out.println(dataSource.getConnection());
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
}
/**
* 获取数据库连接池中的连接
* @return 如果返回null,说明获取连接失败<br/>有值就是获取连接成功
*/
public static Connection getConnection(){
Connection conn = null;
try {
conn = dataSource.getConnection();
} catch (Exception e) {
e.printStackTrace();
}
return conn;
}
/**
* 关闭连接,放回数据库连接池
* @param conn
*/
public static void close(Connection conn){
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
测试代码:
public class JdbcUtilsTest {
@Test
public void testJdbcUtils(){
for (int i = 0; i < 100; i++){
Connection connection = JdbcUtils.getConnection();
System.out.println(connection);
JdbcUtils.close(connection);
}
}
}
根据JdbcUtils实现增删改查操作,编写BaseDao
public abstract class BaseDao {
//使用Dbutils操作数据库
private QueryRunner queryRunner = new QueryRunner();
/**
* update():方法用来执行:Insert/Update/Delete语句
* @param sql sql语句
* @param args update的参数
* @return 如果返回-1,则表示执行失败<br/>返回其他则表示影响的行数
*/
public int update(String sql, Object... args){
Connection connection = JdbcUtils.getConnection();
try {
return queryRunner.update(connection,sql,args);
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
JdbcUtils.close(connection);
}
return -1;
}
/**
* 查询返回一个javaBean的sql语句
* @param type 返回的对象类型
* @param sql 执行的sql语句
* @param args sql对应得参数值
* @param <T> 返回类型的泛型
* @return
*/
public <T> T queryForOne(Class<T> type, String sql, Object... args){
Connection con = JdbcUtils.getConnection();
try {
return queryRunner.query(con,sql,new BeanHandler<T>(type),args);
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
JdbcUtils.close(con);
}
return null;
}
/**
* 查询返回多个javaBean的sql语句
* @param type 返回对象的类型
* @param sql 执行sql的语句
* @param args sql对应得参数值
* @param <T> 返回类型的泛型
* @return
*/
public <T>List<T> queryForList(Class<T> type, String sql, Object... args){
Connection con = JdbcUtils.getConnection();
try {
return queryRunner.query(con,sql,new BeanListHandler<T>(type),args);
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
JdbcUtils.close(con);
}
return null;
}
/**
* 查询返回一行一列的sql语句(查询返回单一值的sql语句,类似于count(*)的值)
* @param sql
* @param args
* @return
*/
public Object queryForSingleValue(String sql, Object... args){
Connection con = JdbcUtils.getConnection();
try {
return queryRunner.query(con,sql,new ScalarHandler(),args);
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
JdbcUtils.close(con);
}
return null;
}
}
定义一个UserDao接口规范,并基于BaseDao父类,可以很容易的创建一个关于用户的实现类UserDaoImpl,实现对数据的增删改查。
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`) values(?,?,?)";
return update(sql,user.getUsername(),user.getPassword(),user.getEmail());
}
}
UserDao类进行测试
public class UserDaoTest {
UserDao userDao = new UserDaoImpl();
@Test
public void queryUserByUsername() {
if(userDao.queryUserByUsername("admin3")==null){
System.out.println("用户名可用");
}else{
System.out.println("用户名已存在");
}
}
@Test
public void queryUserByUsernameAndPassword() {
if(userDao.queryUserByUsernameAndPassword("admin","admin1")==null){
System.out.println("用户名或者密码错误,登录失败");
}else{
System.out.println("查询成功");
}
}
@Test
public void saveUser() {
System.out.println(userDao.saveUser(new User(2,"sgu123","123487","sgu@qq.com")));
}
}
4. Service层
主要是完成一些逻辑业务,本次分项目中,逻辑业务包括注册、登录。首先定义一个UserService接口规范。
public interface UserService {
/**
* 注册用户
* @param user
*/
public void registUser(User user);
/**
* 登录
* @param user
* @return
*/
public User login(User user);
/**
* 检查用户名是否可用
* @param username
* @return 如果返回true则表示用户名已存在,用户名不可用,反之则表示用户表可用
*/
public boolean existUsername(String username);
}
创建一个Service实现类
public class UserServiceImpl implements UserService {
private 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 existUsername(String username) {
if (userDao.queryUserByUsername(username) == null) {
// 等于null,说明没查到,没查到表示可用
return false;
}
return true;
}
}
对Service层进行测试
public class UserServiceTest {
UserService userService = new UserServiceImpl();
@Test
public void registerUser() {
userService.registUser(new User(null,"perfect","perfect123","xk@qq.com"));
}
@Test
public void login() {
System.out.println(userService.login(new User(null, "perfect1", "perfect123", "xk@qq.com")));
}
@Test
public void existUsername() {
if(userService.existUsername("perfect2")==true){
System.out.println("用户已存在");
}else{
System.out.println("用户名可用");
}
}
}
5. Web层
Web层采用Servlet编程,主要负责接收来自客户端的请求,并发送服务器端的响应给客户端,本次分项目涉及两个Servlet程序,分别负责注册和登录两个功能。大体流程如下:
5.1 注册
public class RegistServlet extends HttpServlet {
private UserService userService = new UserServiceImpl();
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1、获取请求的参数
String username = req.getParameter("username");
String password = req.getParameter("password");
String email = req.getParameter("email");
String code = req.getParameter("code");
// 2、检查 验证码是否正确 === 写死,要求验证码为:abcde
if ("abcde".equalsIgnoreCase(code)) {
// 3、检查 用户名是否可用
if (userService.existUsername(username)) {
System.out.println("用户名[" + username + "]已存在!");
// 跳回注册页面
req.getRequestDispatcher("/pages/user/regist.html").forward(req, resp);
} else {
// 可用
// 调用Sservice保存到数据库
userService.registUser(new User(null, username, password, email));
//
// 跳到注册成功页面 regist_success.html
req.getRequestDispatcher("/pages/user/regist_success.html").forward(req, resp);
}
} else {
System.out.println("验证码[" + code + "]错误");
req.getRequestDispatcher("/pages/user/regist.html").forward(req, resp);
}
}
}
5.2 登录
public class LoginServlet extends HttpServlet {
private UserService userService = new UserServiceImpl();
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.获取请求参数
String username = req.getParameter("username");
String password = req.getParameter("password");
//2.调用Userservice中的login服务
User login = userService.login(new User(null, username, password, null));
if(login==null){
//匹配失败,跳回登录界面
req.getRequestDispatcher("/pages/user/login.html").forward(req,resp);
}else{
//匹配成功,跳转到登录成功界面
req.getRequestDispatcher("/pages/user/login_success.html").forward(req,resp);
}
}
}
客户端
此部分功能在我的上一篇博客中已经完成,在之前完成的基础上,主要变化有两处:(1)添加了相对路径的概念,设置一个针对此html代码的固定相对路径,之后所有的代码都是基于这个路径上的。(2)设置form action,当点击提交按钮后,form 表单提交到不同的servlet程序中,提交方式有get和post,本次项目采用post方法进行提交。
<base href="http://localhost:8080/book/"> (1)
<form action="registServlet" method="post"> (2)
Bug调试
在本次项目中,主要是遇到了两个特别愚蠢的错误,(1)首先是在web.xml配置中,servlet-mapping配置的地方忘记加“/”,导致我工程直接访问不到页面,即卡死在了html显示那一步.(2)在regist.xml中,相对路径配置的有问题,导致每次点击提交后,客户端的请求无法传递给Tomcat服务器。
(本博客仅仅是个人简单记录,如有错误,还请帮忙指出,感激不尽)