likes
comments
collection
share

11、书城项目第二阶段

作者站长头像
站长
· 阅读数 77

11、书城项目第二阶段

一、不带数据库的登录注册

1、创建动态Web工程

11、书城项目第二阶段

2、把V1中的页面粘贴过来

11、书城项目第二阶段

3、使用base标签统一设置路径基准

base标签的语法规则

  • base标签要写在head标签内
  • base标签必须写在所有其他有路径的标签的前面
  • base标签使用href属性设置路径的基准
  • base标签生效的机制是:最终的访问地址=base标签href属性设置的基准+具体标签内的路径
  • 对于想要参考base标签的具体标签,如果路径是以斜杠开头,那么它将不参考base标签

base标签使用举例

<head>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <title>书城首页</title>
    <base href="/bookstore/"/>
    <link rel="stylesheet" href="static/css/minireset.css"/>
    <link rel="stylesheet" href="static/css/common.css"/>
    <link rel="stylesheet" href="static/css/iconfont.css"/>
    <link rel="stylesheet" href="static/css/index.css"/>
    <link rel="stylesheet" href="static/css/swiper.min.css"/>
</head>

4、基于base标签调整整个页面的路径

base标签的代码

在需要的页面把下面的base标签代码粘贴到head标签内、需要路径的标签前即可

<base href="/bookstore/"/>

对需要统一调整的路径执行替换

Ctrl+r调出替换操作窗口

11、书城项目第二阶段

具体操作时请参考页面的实际情况进行替换。

5、基本假设

为了实现『不带数据库』的登录注册,我们需要假设:系统中目前已有用户:

用户名密码
tom123456

6、登录功能

明确目标

在服务器端检查用户通过表单提交的用户名、密码是否正确。

  • 成功:跳转到login_success.html页面
  • 失败:返回错误消息

思路

友情提示:分析业务功能,捋清思路最好的办法就是画流程图

11、书城项目第二阶段

代码

创建LoginServlet

11、书城项目第二阶段

创建Packages时的规范:

公司或组织域名倒序.项目名.模块名.具体包名

公司或组织域名倒序.项目名.具体包名

下面是完整的Servlet配置信息:

<servlet>
    <servlet-name>LoginServlet</servlet-name>
    <servlet-class>com.atguigu.bookstore.servlet.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>LoginServlet</servlet-name>
    <url-pattern>/LoginServlet</url-pattern>
</servlet-mapping>
完成doPost()方法中的代码
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    // 1.声明两个变量,用于存储假设的用户名、密码
    String usernameSystem = "tom";
    String passwordSystem = "123456";

    // 2.获取请求参数中的用户名、密码
    String usernameForm = request.getParameter("username");
    String passwordForm = request.getParameter("password");

    // 3.执行判断
    if(usernameSystem.equals(usernameForm) && passwordSystem.equals(passwordForm)) {

        // 4.登录成功:重定向到登录成功页面
        response.sendRedirect(request.getContextPath() + "/pages/user/login_success.html");

    }else{

        // 5.登录失败
        response.setContentType("text/html;charset=UTF-8");
        response.getWriter().write("抱歉!用户名或密码不正确,请重新输入!");

    }

}
HTML页面设置表单提交地址
<form id="loginForm" action="LoginServlet" method="post">
    <label>用户名称:</label>
    <input class="itxt" type="text" v-model:value="username" placeholder="请输入用户名" autocomplete="off" tabindex="1" name="username" id="username" />
    <br />
    <br />
    <label>用户密码:</label>
    <input class="itxt" type="password" v-model:value="password" placeholder="请输入密码" autocomplete="off" tabindex="1" name="password" id="password" />
    <br />
    <br />
    <button type="submit" id="sub_btn" @click="loginCheck">登录</button>
</form>

提示消息改进探索

以下代码仅供参考:

// 5.登录失败
// 返回提示消息方案一:过于简陋
// response.setContentType("text/html;charset=UTF-8");
// response.getWriter().write("抱歉!用户名或密码不正确,请重新输入!");

// 返回提示消息方案二:没有提示消息,让用户非常困惑
// request.getRequestDispatcher("/pages/user/login.html").forward(request, response);

// 返回提示消息方案三:确实能在登录页面显示提示消息,但是实现的方式让我想骂人
response.setContentType("text/html;charset=UTF-8");
PrintWriter writer = response.getWriter();
writer.write("<!DOCTYPE html>");
writer.write("<html>");
writer.write("    <head>");
writer.write("        <base href='/bookstore/' />");

7、注册功能

明确目标

用户提交注册表单后,检查用户名是否被占用

  • 没有被占用:注册成功
  • 已经被占用:注册失败

思路

11、书城项目第二阶段

代码

创建RegisterServlet

11、书城项目第二阶段

完整配置信息:

<servlet>
    <servlet-name>RegisterServlet</servlet-name>
    <servlet-class>com.atguigu.bookstore.servlet.RegisterServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>RegisterServlet</servlet-name>
    <url-pattern>/RegisterServlet</url-pattern>
</servlet-mapping>
完成doPost()方法中的代码
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    // 1.声明变量保存系统内置的用户名
    String usernameSystem = "jerry";

    // 2.获取请求参数
    String usernameForm = request.getParameter("username");

    // 3.比较
    if (usernameSystem.equals(usernameForm)) {

        // 4.说明用户名已经被占用,需要提示错误消息
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter writer = response.getWriter();
        writer.write("抱歉!用户名已经被占用,请重新输入!");

    }else{

        // 5.说明用户名可用,跳转到注册成功页面
        response.sendRedirect(request.getContextPath() + "/pages/user/regist_success.html");

    }

}
HTML页面调整表单中的设置
<form id="registerForm" action="RegisterServlet" method="post">
    <div class="form-item">
        <div>
            <label>用户名称:</label>
            <input v-model:value="username" type="text" name="username" placeholder="请输入用户名" />
            <span></span>
        </div>
        <span>{{usernameCheckMessage}}</span>
    </div>
    <div class="form-item">
        <div>
            <label>用户密码:</label>
            <input v-model:value="password" type="password" name="password" placeholder="请输入密码" />
        </div>
        <span class="errMess">密码的长度至少为8位</span>
    </div>
    <div class="form-item">
        <div>
            <label>确认密码:</label>
            <input v-model:value="passwordConfirm" type="password" placeholder="请输入确认密码" />
        </div>
        <span class="errMess">密码两次输入不一致</span>
    </div>
    <div class="form-item">
        <div>
            <label>用户邮箱:</label>
            <input v-model:value="email" type="text" name="email" placeholder="请输入邮箱" />
        </div>
        <span class="errMess">请输入正确的邮箱格式</span>
    </div>
    <div class="form-item">
        <div>
            <label>验证码:</label>
            <div class="verify">
                <input v-model:value="code" type="text" name="code" placeholder="" />
                <img src="static/img/code.bmp" alt="" />
            </div>
        </div>
        <span class="errMess">请输入正确的验证码</span>
    </div>
    <button type="submit" @click="registerCheck" class="btn">注册</button>
</form>

二、三层架构

1、三层架构划分

11、书城项目第二阶段

  • 表述层:负责处理浏览器请求、返回响应、页面调度
  • 业务逻辑层:负责处理业务逻辑,根据业务逻辑把持久化层从数据库查询出来的数据进行运算、组装,封装好后返回给表述层,也可以根据业务功能的需要调用持久化层把数据保存到数据库、修改数据库中的数据、删除数据库中的数据
  • 持久化层:根据上一层的调用对数据库中的数据执行增删改查的操作

2、三层架构好处

如果不做三层架构形式的拆分:

11、书城项目第二阶段

所有和当前业务功能需求相关的代码全部耦合在一起,如果其中有任何一个部分出现了问题,牵一发而动全身,导致其他无关代码也要进行相应的修改。这样的话代码会非常难以维护。

所以为了提高开发效率,需要对代码进行模块化的拆分。整个项目模块化、组件化程度越高,越容易管理和维护,出现问题更容易排查。

3、三层架构和模型的关系

11、书城项目第二阶段

模型对整个项目中三层架构的每一层都提供支持,具体体现是使用模型对象封装业务功能数据

Java实体类有很多不同名称:

  • POJO:Plain old Java Object,传统的普通的Java对象
  • entity:实体类
  • bean或Java bean
  • domain:领域模型

4、模型开发的要求

ORM

ORM:Object Relative Mapping对象关系映射

对象:Java对象

关系:关系型数据库

映射:Java对象和数据库表之间的对应关系

Java类数据库表
属性字段/列
对象记录/行
属性按照驼峰式命名字段名各个单词之间用下划线分开

Java实体类的要求

  • 必须有一个无参构造器

    将来使用框架后,大量的对象都是框架通过反射来创建的。

    Class< T > clazz = Class.forName("全类名");

    clazz.newInstance();

  • 通过getXxx()、setXxx()方法定义属性:getXxx()或setXxx()方法去掉get或set后,Xxx把首字母小写,得到的xxx就是属性名。

public class User {

    private String safeUserName;

    public String getUserName(){
        return this.safeUserName;
    }

    public void setUserName(String userName){
        this.safeUserName = userName;
    }

}

在上面例子中,getXxx()、setXxx()方法定义的属性是userName,不是safeUserName。

三、建模

1、创建数据库

CREATE DATABASE bookstore210107 CHARACTER SET utf8;
USE `bookstore210107`;

2、创建数据库表

物理建模的思路参考这里

CREATE TABLE t_user(
    user_id INT PRIMARY KEY AUTO_INCREMENT,
    user_name CHAR(100),
    user_pwd CHAR(100),
    email CHAR(100)
);

3、创建Java实体类

11、书城项目第二阶段

11、书城项目第二阶段

public class User {

    private Integer userId;// user_id
    private String userName;// user_name
    private String userPwd;// user_pwd
    private String email;// email
    ……

四、持久化层

1、加入所需jar包

11、书城项目第二阶段

11、书城项目第二阶段

11、书城项目第二阶段

2、创建连接数据库的工具类

11、书城项目第二阶段

3、创建外部属性文件

11、书城项目第二阶段

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/bookstore210107
username=root
password=123456
initialSize=10
maxActive=20
maxWait=10000

注意:这里是上课演示时使用的参数,大家操作时要改成自己的。

4、在JDBCUtils类中创建数据源对象

private static DataSource dataSource;

static {

    // 1.创建一个用于存储外部属性文件信息的Properties对象
    Properties properties = new Properties();

    // 2.使用当前类的类加载器加载外部属性文件:jdbc.properties
    InputStream inputStream = JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");

    try {

        // 3.将外部属性文件jdbc.properties中的数据加载到properties对象中
        properties.load(inputStream);

        // 4.创建数据源对象
        dataSource = DruidDataSourceFactory.createDataSource(properties);

    } catch (Exception e) {
        e.printStackTrace();
    }

}

5、声明工具方法操作数据库连接

    /**
     * 从数据源中获取数据库连接
     * @return 数据库连接对象
     */
    public static Connection getConnection() {

        Connection connection = null;

        try {
            connection = dataSource.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }

        return connection;

    }

    /**
     * 释放数据库连接
     * @param connection 要执行释放操作的连接对象
     */
    public static void releaseConnection(Connection connection) {

        if (connection != null) {

            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }

        }

    }

测试代码如下:

public class BookstoreTest {

    @Test
    public void testConnection() {

        Connection connection = JDBCUtils.getConnection();
        System.out.println("connection = " + connection);

    }

}

6、创建BaseDao

DAO概念

DAO:Data Access Object数据访问对象

DAL:Data Access Layer数据访问层

创建Java类

11、书城项目第二阶段

编写通用方法

/**
 * 各个具体Dao类的基类,泛型T对应具体实体类类型
 * @param <T>
 */
public class BaseDao<T> {

    private QueryRunner queryRunner = new QueryRunner();

    /**
     * 通用的增删改方法
     * @param sql 要执行的SQL语句
     * @param param 为SQL语句准备好的参数
     * @return 受影响的行数
     */
    public int update(String sql, Object ... param) {

        int updatedRowCount = 0;

        Connection connection = JDBCUtils.getConnection();

        try {

            updatedRowCount = queryRunner.update(connection, sql, param);

        }
        // 为了让上层方法调用方便,将编译时异常捕获
        catch (SQLException e) {
            e.printStackTrace();

            // 为了不掩盖问题,将编译时异常封装为运行时异常抛出
            throw new RuntimeException(e);
        } finally {

            // 关闭数据库连接
            JDBCUtils.releaseConnection(connection);

        }

        return updatedRowCount;

    }

    /**
     * 查询单个对象
     * @param clazz 单个对象所对应的实体类类型
     * @param sql   查询单个对象所需要的SQL语句
     * @param param SQL语句的参数
     * @return      查询到的单个对象
     */
    public T getBean(Class<T> clazz, String sql, Object ... param) {

        Connection connection = JDBCUtils.getConnection();

        T t = null;

        try {

            t = queryRunner.query(connection, sql, new BeanHandler<>(clazz), param);
        } catch (SQLException e) {
            e.printStackTrace();

            throw new RuntimeException(e);
        } finally {

            // 关闭数据库连接
            JDBCUtils.releaseConnection(connection);

        }

        return t;
    }

    /**
     * 查询集合对象
     * @param clazz 集合中单个对象所对应的实体类类型
     * @param sql   查询集合所需要的SQL语句
     * @param param SQL语句的参数
     * @return      查询到的集合对象
     */
    public List<T> getBeanList(Class<T> clazz, String sql, Object ... param) {

        Connection connection = JDBCUtils.getConnection();

        List<T> list = null;

        try {

            list = queryRunner.query(connection, sql, new BeanListHandler<>(clazz), param);
        } catch (SQLException e) {
            e.printStackTrace();

            throw new RuntimeException(e);
        } finally {

            // 关闭数据库连接
            JDBCUtils.releaseConnection(connection);

        }

        return list;
    }

}

测试方法:

@Test
public void testUpdate() {
    BaseDao<Object> baseDao = new BaseDao<>();

    String sql = "insert into t_user(user_name,user_pwd) values(?,?)";

    int count = baseDao.update(sql, "罗志祥", "789456");

    System.out.println("count = " + count);
}

@Test
public void testGetBean() {
    BaseDao<User> baseDao = new BaseDao<>();

    // user_id userId
    // user_name userName
    // user_pwd userPwd
    String sql = "select user_id userId,user_name userName,user_pwd userPwd from t_user where user_id=?";

    User user = baseDao.getBean(User.class, sql, 2);

    System.out.println("user = " + user);
}

@Test
public void testGetBeanList() {
    BaseDao<User> baseDao = new BaseDao<>();

    String sql = "select user_id userId,user_name userName,user_pwd userPwd from t_user";

    List<User> userList = baseDao.getBeanList(User.class, sql);

    for (User user : userList) {
        System.out.println("user = " + user);
    }
}

7、创建UserDao

用户登录注册功能中的组件关系图

11、书城项目第二阶段

声明UserDao接口

11、书城项目第二阶段

public interface UserDao {

    /**
     * 根据用户名查询User对象
     * @param username 用户名
     * @return 查询到的User对象
     */
    User selectUserByName(String username);

    /**
     * 将User对象保存到数据库
     * @param user 要保存的User对象
     * @return 受影响的行数
     */
    int insertUser(User user);

}

声明UserDaoImpl实现类

public class UserDaoImpl extends BaseDao<User> implements UserDao {
    @Override
    public User selectUserByName(String username) {

        String sql = "select user_id userId,user_name userName,user_pwd userPwd,email from t_user where user_name=?";

        return super.getBean(User.class, sql, username);
    }

    @Override
    public int insertUser(User user) {

        String sql = "insert into t_user(user_name,user_pwd,email) values(?,?,?)";

        return super.update(sql, user.getUserName(), user.getUserPwd(), user.getEmail());
    }
}

测试方法

@Test
public void testUserDaoGetUserByName() {

    UserDao userDao = new UserDaoImpl();

    User user = userDao.selectUserByName("陈冠希");

    System.out.println("user = " + user);

}

@Test
public void testUserDaoSaveUser() {
    UserDao userDao = new UserDaoImpl();

    User user = new User(null, "陈冠希", "666666", "aaa@qq.com");

    int count = userDao.insertUser(user);

    System.out.println("count = " + count);
}

五、完成带数据库的登录注册

1、密码加密

加密方式介绍

  • 对称加密:在知道密文和加密算法的情况下,能够反推回明文
  • 非对称加密:
    • 加密:使用私钥加密
    • 解密:使用公钥解密

加密算法:HASH

  • 特点1:不可逆
  • 特点2:加密后,密文长度固定
  • 特点3:输入数据不变,输出数据也保证不变;输入数据变化,输出数据一定变化

常见的HASH算法举例:

  • MD5
  • SHA1
  • SHA512
  • CRC32

执行加密的工具方法

public class MD5Util {

    /**
     * 针对明文字符串执行MD5加密
     * @param source
     * @return
     */
    public static String encode(String source) {

        // 1.判断明文字符串是否有效
        if (source == null || "".equals(source)) {
            throw new RuntimeException("用于加密的明文不可为空");
        }

        // 2.声明算法名称
        String algorithm = "md5";

        // 3.获取MessageDigest对象
        MessageDigest messageDigest = null;
        try {
            messageDigest = MessageDigest.getInstance(algorithm);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }

        // 4.获取明文字符串对应的字节数组
        byte[] input = source.getBytes();

        // 5.执行加密
        byte[] output = messageDigest.digest(input);

        // 6.创建BigInteger对象
        int signum = 1;
        BigInteger bigInteger = new BigInteger(signum, output);

        // 7.按照16进制将bigInteger的值转换为字符串
        int radix = 16;
        String encoded = bigInteger.toString(radix).toUpperCase();

        return encoded;
    }

}

2、注册功能

目标

检查用户名是否可用,如果用户名可用则保存User对象

思路

11、书城项目第二阶段

代码

[1]创建UserService

11、书城项目第二阶段

public interface UserService {

    void doRegister(User userForm);

    User doLogin(User userForm);

}

开发中,接口设计和接口中方式定义的理念:

  • 方法的返回值应该对应这个方法本身的业务功能
    • 写操作:没有返回值
    • 读操作:有返回值,返回值就是查询的结果
  • 方法执行是否成功
    • 成功:不抛异常
    • 失败:抛异常

启发:

上层方法向下层方法布置任务:方法名、方法的参数

下层方法向上层方法反馈结果:返回值、是否抛异常

[2]实现UserService接口

11、书城项目第二阶段

11、书城项目第二阶段

(1)UserDao声明为成员变量

说明:将来在Servlet中使用Service的时候,也是同样声明为成员变量,那么从Servlet、Service到Dao就都是**『单实例,多线程』**方式运行。

public class UserServiceImpl implements UserService {

    private UserDao userDao = new UserDaoImpl();

    @Override
    public void doRegister(User userForm) {

    }

    @Override
    public User doLogin(User userForm) {

        return null;
    }
}

理由:

  • 创建对象的操作只执行一次
  • 对象在内存中只保存一份,不会过多占用内存空间
(2)实现注册功能
@Override
public void doRegister(User userForm) {

    // 1.从userForm对象中获取用户通过表单提交的用户名
    String userName = userForm.getUserName();

    // 2.根据用户名调用UserDao方法查询对应的User对象
    User userDB = userDao.selectUserByName(userName);

    // 3.检查User对象是否为空
    if (userDB != null) {
        // 4.如果User对象不为空,则抛出异常,通知上层调用方法:用户名已经被占用
        throw new RuntimeException("用户名已经被占用");
    }

    // 5.对表单提交的密码执行MD5加密
    // ①取出表单的密码
    String userPwd = userForm.getUserPwd();

    // ②执行加密
    String encode = MD5Util.encode(userPwd);

    // ③将加密得到的密文字符串设置回userForm对象
    userForm.setUserPwd(encode);

    // 6.调用UserDao方法将userForm对象保存到数据库
    userDao.insertUser(userForm);
}
[3]修改RegisterServlet
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    // 1.从请求参数中获取数据封装为User对象
    String username = request.getParameter("username");
    String password = request.getParameter("password");
    String email = request.getParameter("email");

    User userForm = new User(null, username, password, email);

    // 2.调用UserService的方法执行注册
    try {
        userService.doRegister(userForm);

        // 3.如果没有抛出异常那么就跳转到注册成功的页面
        // 选择重定向的原因:跳转到regist_success.html页面后,用户刷新浏览器不会重复提交注册表单
        response.sendRedirect(request.getContextPath()+"/pages/user/regist_success.html");
    } catch (Exception e) {
        e.printStackTrace();

        // 4.如果抛出了异常
        response.setContentType("text/html;charset=UTF-8");
        response.getWriter().write("注册失败:" + e.getMessage());
    }

}

3、登录功能

关键点提示

对用户密码进行验证时,无法将密文解密为明文,只能将明文再次加密为密文,『比较密文是否一致』

思路

11、书城项目第二阶段

代码

[1]UserService接口的doLogin()
@Override
public User doLogin(User userForm) {

    // 1.获取表单提交的用户名
    String userName = userForm.getUserName();

    // 2.根据用户名调用UserDao方法查询User对象
    User userDB = userDao.selectUserByName(userName);

    // 3.检查数据库查询的User对象是否为null
    if (userDB == null) {
        // 4.如果数据库查询的User对象为null,说明用户名不正确,抛出异常:登录失败
        throw new RuntimeException("用户名或密码不正确");
    }

    // 5.密码验证
    // ①获取表单提交的密码
    String userPwdForm = userForm.getUserPwd();

    // ②对表单提交的密码进行加密
    String encode = MD5Util.encode(userPwdForm);

    // ③获取数据库查询到的密码
    String userPwdDB = userDB.getUserPwd();

    // ④比较表单密码和数据库密码
    if (Objects.equals(encode, userPwdDB)) {
        // 6.如果密码验证成功,则将从数据库查询出来的User对象返回
        return userDB;

    }else{
        // 7.如果密码验证失败,说明密码不正确,抛出异常:登录失败
        throw new RuntimeException("用户名或密码不正确");
    }

}
[2]LoginServlet的doPost()方法
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    // 1.从请求参数中获取用户名和密码
    String username = request.getParameter("username");
    String password = request.getParameter("password");

    // 2.封装为User对象
    User userForm = new User(null, username, password, null);

    // 3.调用UserService的方法执行登录验证
    try {
        User userDB = userService.doLogin(userForm);

        // 4.登录成功后跳转到登录成功页面
        response.sendRedirect(request.getContextPath() + "/pages/user/login_success.html");
    } catch (Exception e) {
        e.printStackTrace();

        // 5.登录失败则显示提示消息
        response.setContentType("text/html;charset=UTF-8");
        response.getWriter().write("登录失败:" + e.getMessage());
    }

}
转载自:https://juejin.cn/post/7215587423686017061
评论
请登录