JavaWeb阶段性项目2:QQZone项目梳理

前置知识

前置准备

知识准备

已掌握JavaSE/MySQL/JDBC+HTML/CSS/JavaScript基础

并已完成了Javaweb前置知识的学习

01-JavaWeb-HTML初识

02-JavaWeb-CSS初识

03-JavaWeb-JavaScript初识

04-JavaScript基础应用-鼠标悬浮/离开表格格式变化

05-JavaWeb-Tomcat8安装、Servlet初识

06-JavaWeb-Servlet方法/生命周期、HTTP/会话session

07-JavaWeb-视图模板技术Thymeleaf的使用

08-JavaWeb-Servlet保存作用域

09-JavaWeb-阶段性项目1:最简单的后台库存管理系统

  10-JavaWeb阶段性项目1:系统的servlet优化1 

  11-JavaWeb阶段性项目1:系统的servlet优化2

  12-JavaWeb阶段性项目1:系统的servlet优化3

  13-JavaWeb阶段性项目1:系统的servlet优化4

  14-JavaWeb阶段性项目1:系统的servlet优化5

15-JavaWeb阶段性项目1:Servlet-api、mvc-service引入、IOC和DI

资源准备

尚硅谷丨2022版JavaWeb教程视频

教学资源

https://pan.baidu.com/s/1TS7QJ_a2vHHmXkggAs8RMQ

提取码:yyds


本文已经完成了视频P55前的所有内容,接下来是一个练手项目,可以很好的巩固之前视频的知识,同时更接近真实项目开发,从理解需求开始,进行一个简单Javaweb项目的设计与实现——尚硅谷2022Javaweb视频教程P56-QQZone项目

以下主要是实现过程及思路的整理,不过多涉及代码如何实现。

1 需求分析

 视频中需求
 
 首先熟悉需求
​
    1) 用户登录
    2) 登录成功,显示主界面。左侧显示好友列表;上端显示欢迎词。如果不是自己的空间,显示超链接:返回自己的空间;下端显示日志列表
    3) 查看日志详情:
       - 日志本身的信息(作者头像、昵称、日志标题、日志内容、日志的日期)
       - 回复列表(回复者的头像、昵称、回复内容、回复日期)
       - 主人回复信息
    4) 删除日志
    5) 删除特定回复
    6) 删除特定主人回复
    7) 添加日志、添加回复、添加主人回复
    8) 点击左侧好友链接,进入好友的空间

需求分析需要对所进行项目的相关实际业务非常了解,确保深入理解了用户端的业务逻辑、表单结构、管理规范等。

用原型图的方式展示给用户以了解和挖掘用户的真实需求,重点是确认需求不会再有更改(如果是外包可以通过签订合同,如果是自研可以走OA流程最终确定)

2 数据库设计

2.1 概念结构设计

1) 抽取实体 : 用户登录信息、用户详情信息 、 日志 、 回贴  、 主人回复
2) 分析其中的属性:
  - 用户登录信息:账号、密码、头像、昵称
  - 用户详情信息:真实姓名、星座、血型、邮箱、手机号.....
  - 日志:标题、内容、日期、作者
  - 回复:内容、日期、作者、日志
  - 主人回复:内容、日期、作者、回复
3) 分析实体之间的关系
  - 用户登录信息 : 用户详情信息      1:1 PK
  - 用户 : 日志                   1:N
  - 日志 : 回复                   1:N
  - 回复 : 主人回复                1:1 UK
  - 用户 : 好友                   M : N

按照规范设计,我们将数据库的设计过程分为六个阶段:

  • 系统需求分析阶段;

  • 概念结构设计阶段;

  • 逻辑结构设计阶段;

  • 物理结构设计阶段;

  • 数据库实施阶段;

  • 数据库运行与维护阶段;

这里只进行了业务级别的概念结构设计,找到了实体,分析了实体的属性,建立了实体与实体之间的联系,也就是E-R模型图的内容。

对于实体的考虑

视频中老师抽取实体时不是完全描述现实世界的方式,而是根据实际业务来设计,比如将用户分为了用户登录信息、用户详情信息两个实体

对于属性的考虑

需要平衡数据库的性能和规范度

选择空间还是时间?要根据实际业务来,查询频次低的选择空间、提高规范度,频次高的选择时间,反范式化。

2.2 逻辑结构设计

创建表的SQL代码

CREATE` `DATABASE` `qqzonedb ``CHAR` `SET` `utf8;
USE qqzonedb;

 

CREATE` `TABLE` ``t_user_basic` (
 ```id` ``INT``(11) ``NOT` `NULL` `AUTO_INCREMENT,
 ```loginId` ``VARCHAR``(20) ``NOT` `NULL``,
 ```nickName` ``VARCHAR``(50) ``NOT` `NULL``,
 ```pwd` ``VARCHAR``(20) ``NOT` `NULL``,
 ```headImg` ``VARCHAR``(20) ``DEFAULT` `NULL``,
 ``PRIMARY` `KEY` `(`id`),
 ``UNIQUE` `KEY` ``loginId` (`loginId`)
) ENGINE=INNODB AUTO_INCREMENT=6 ``DEFAULT` `CHARSET=utf8;

 

INSERT` `INTO` ``t_user_basic`(`id`,`loginId`,`nickName`,`pwd`,`headImg`) 
VALUES` `(1,``'u001'``,``'jim'``,``'ok'``,``'h1.jpeg'``),
(2,``'u002'``,``'tom'``,``'ok'``,``'h2.jpeg'``),
(3,``'u003'``,``'kate'``,``'ok'``,``'h3.jpeg'``),
(4,``'u004'``,``'lucy'``,``'ok'``,``'h4.jpeg'``),
(5,``'u005'``,``'张三丰'``,``'ok'``,``'h5.jpeg'``);

 

CREATE` `TABLE` ``t_user_detail` (
 ```id` ``INT``(11) ``NOT` `NULL``,
 ```realName` ``VARCHAR``(20) ``DEFAULT` `NULL``,
 ```tel` ``VARCHAR``(11) ``DEFAULT` `NULL``,
 ```email` ``VARCHAR``(30) ``DEFAULT` `NULL``,
 ```birth` DATETIME ``DEFAULT` `NULL``,
 ```star` ``VARCHAR``(10) ``DEFAULT` `NULL``,
 ``PRIMARY` `KEY` `(`id`),
 ``CONSTRAINT` ``FK_detail_basic` ``FOREIGN` `KEY` `(`id`) ``REFERENCES` ``t_user_basic` (`id`)
) ENGINE=INNODB ``DEFAULT` `CHARSET=utf8;

 

CREATE` `TABLE` ``t_friend` (
 ```id` ``INT``(11) ``NOT` `NULL` `AUTO_INCREMENT,
 ```uid` ``INT``(11) ``DEFAULT` `NULL``,
 ```fid` ``INT``(11) ``DEFAULT` `NULL``,
 ``PRIMARY` `KEY` `(`id`),
 ``KEY` ``FK_friend_basic_uid` (`uid`),
 ``KEY` ``FK_friend_basic_fid` (`fid`),
 ``CONSTRAINT` ``FK_friend_basic_fid` ``FOREIGN` `KEY` `(`fid`) ``REFERENCES` ``t_user_basic` (`id`),
 ``CONSTRAINT` ``FK_friend_basic_uid` ``FOREIGN` `KEY` `(`uid`) ``REFERENCES` ``t_user_basic` (`id`)
) ENGINE=INNODB AUTO_INCREMENT=11 ``DEFAULT` `CHARSET=utf8;

 

 

INSERT` `INTO` ``t_friend`(`id`,`uid`,`fid`) 
VALUES` `(1,1,2),(2,1,3),(3,1,4),(4,1,5),(5,2,3),(6,2,1),(7,2,4),(8,3,1),(9,3,2),(10,5,1);

 

CREATE` `TABLE` ``t_topic` (
 ```id` ``INT``(11) ``NOT` `NULL` `AUTO_INCREMENT,
 ```title` ``VARCHAR``(100) ``NOT` `NULL``,
 ```content` ``VARCHAR``(500) ``NOT` `NULL``,
 ```topicDate` DATETIME ``NOT` `NULL``,
 ```author` ``INT``(11) ``NOT` `NULL``,
 ``PRIMARY` `KEY` `(`id`),
 ``KEY` ``FK_topic_basic` (`author`),
 ``CONSTRAINT` ``FK_topic_basic` ``FOREIGN` `KEY` `(`author`) ``REFERENCES` ``t_user_basic` (`id`)
) ENGINE=INNODB AUTO_INCREMENT=9 ``DEFAULT` `CHARSET=utf8;

 

INSERT` `INTO` ``t_topic`(`id`,`title`,`content`,`topicDate`,`author`) 
VALUES` `(3,``'我的空间开通了,先做自我介绍!'``,``'大家好,我是铁锤妹妹!'``,``'2021-06-18 11:25:30'``,2),(8,``'我的空间'``,``'我的空间'``,``'2021-07-14 16:16:40'``,1);

 

CREATE` `TABLE` ``t_reply` (
 ```id` ``INT``(11) ``NOT` `NULL` `AUTO_INCREMENT,
 ```content` ``VARCHAR``(500) ``NOT` `NULL``,
 ```replyDate` DATETIME ``NOT` `NULL``,
 ```author` ``INT``(11) ``NOT` `NULL``,
 ```topic` ``INT``(11) ``NOT` `NULL``,
 ``PRIMARY` `KEY` `(`id`),
 ``KEY` ``FK_reply_basic` (`author`),
 ``KEY` ``FK_reply_topic` (`topic`),
 ``CONSTRAINT` ``FK_reply_basic` ``FOREIGN` `KEY` `(`author`) ``REFERENCES` ``t_user_basic` (`id`),
 ``CONSTRAINT` ``FK_reply_topic` ``FOREIGN` `KEY` `(`topic`) ``REFERENCES` ``t_topic` (`id`)
) ENGINE=INNODB AUTO_INCREMENT=6 ``DEFAULT` `CHARSET=utf8;

 

INSERT` `INTO` ``t_reply`(`id`,`content`,`replyDate`,`author`,`topic`) 
VALUES` `(3,``'回复'``,``'2021-07-14 16:16:54'``,2,8),
(4,``'回复2222'``,``'2021-07-14 16:17:11'``,3,8),
(5,``'这里是第三个回复'``,``'2021-07-14 16:30:49'``,1,8);

 

CREATE` `TABLE` ``t_host_reply` (
 ```id` ``INT``(11) ``NOT` `NULL` `AUTO_INCREMENT,
 ```content` ``VARCHAR``(500) ``NOT` `NULL``,
 ```hostReplyDate` DATETIME ``NOT` `NULL``,
 ```author` ``INT``(11) ``NOT` `NULL``,
 ```reply` ``INT``(11) ``NOT` `NULL``,
 ``PRIMARY` `KEY` `(`id`),
 ``KEY` ``FK_host_basic` (`author`),
 ``KEY` ``FK_host_reply` (`reply`),
 ``CONSTRAINT` ``FK_host_basic` ``FOREIGN` `KEY` `(`author`) ``REFERENCES` ``t_user_basic` (`id`),
 ``CONSTRAINT` ``FK_host_reply` ``FOREIGN` `KEY` `(`reply`) ``REFERENCES` ``t_reply` (`id`)
) ENGINE=INNODB ``DEFAULT` `CHARSET=utf8;

2.3 物理结构设计

3 系统代码实现

3.1 pojo、DAO、service

POJO(Plain Ordinary Java Object)简单的Java对象,实际就是普通JavaBeans

pojo

将数据库的表转化为pojo

可以通过IDEA将表结构直接身成为是实体类

DAO

数据访问对象接口(Data Access Object Interface)

创建DAO接口的实现类,继承BaseDAO

BaseDAO
package com.atguigu.myssm.basedao;
​
import java.lang.reflect.*;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
​
public abstract class BaseDAO<T> {
    protected Connection conn ;
    protected PreparedStatement psmt ;
    protected ResultSet rs ;
​
    //T的Class对象
    private Class entityClass ;
​
    public BaseDAO() {
        //getClass() 获取Class对象,当前我们执行的是new FruitDAOImpl() , 创建的是FruitDAOImpl的实例
        //那么子类构造方法内部首先会调用父类(BaseDAO)的无参构造方法
        //因此此处的getClass()会被执行,但是getClass获取的是FruitDAOImpl的Class
        //所以getGenericSuperclass()获取到的是BaseDAO的Class
        Type genericType = getClass().getGenericSuperclass();
        //ParameterizedType 参数化类型
        Type[] actualTypeArguments = ((ParameterizedType) genericType).getActualTypeArguments();
        //获取到的<T>中的T的真实的类型
        Type actualType = actualTypeArguments[0];
​
        try {
            entityClass = Class.forName(actualType.getTypeName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            throw new DAOException("BaseDAO 构造方法出错了,可能的原因是没有指定<>中的类型");
        }
​
    }
​
    protected Connection getConn(){
        return ConnUtil.getConn();
    }
​
    protected void close(ResultSet rs , PreparedStatement psmt , Connection conn){
​
    }
​
    //给预处理命令对象设置参数
    private void setParams(PreparedStatement psmt , Object... params) throws SQLException {
        if(params!=null && params.length>0){
            for (int i = 0; i < params.length; i++) {
                psmt.setObject(i+1,params[i]);
            }
        }
    }
​
    //执行更新,返回影响行数
    protected int executeUpdate(String sql , Object... params) {
        boolean insertFlag = false ;
        insertFlag = sql.trim().toUpperCase().startsWith("INSERT");
​
        conn = getConn();
        try{
            if(insertFlag){
                psmt = conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
            }else {
                psmt = conn.prepareStatement(sql);
            }
            setParams(psmt,params);
            int count = psmt.executeUpdate() ;
​
            if(insertFlag){
                rs = psmt.getGeneratedKeys();
                if(rs.next()){
                    return ((Long)rs.getLong(1)).intValue();
                }
            }
            return 0 ;
        }catch (SQLException e){
            e.printStackTrace();
            throw new DAOException("BaseDAO executeUpdate出错了");
        }
    }
​
    //通过反射技术给obj对象的property属性赋propertyValue值
    private void setValue(Object obj ,  String property , Object propertyValue) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException {
        Class clazz = obj.getClass();
​
        //获取property这个字符串对应的属性名 , 比如 "fid"  去找 obj对象中的 fid 属性
        Field field = clazz.getDeclaredField(property);
        if(field!=null){
​
            //获取当前字段的类型名称
            String typeName = field.getType().getName();
            //判断如果是自定义类型,则需要调用这个自定义类的带一个参数的构造方法,创建出这个自定义的实例对象,然后将实例对象赋值给这个属性
​
            if(isMyType(typeName)){
                //假设typeName是"com.atguigu.qqzone.pojo.UserBasic"
                Class typeNameClass = Class.forName(typeName);
                Constructor constructor = typeNameClass.getDeclaredConstructor(java.lang.Integer.class);
                propertyValue = constructor.newInstance(propertyValue);
            }
            field.setAccessible(true);
            field.set(obj,propertyValue);
        }
    }
​
    private static boolean isNotMyType(String typeName){
        return "java.lang.Integer".equals(typeName)
                || "java.lang.String".equals(typeName)
                || "java.util.Date".equals(typeName)
                || "java.sql.Date".equals(typeName);
    }
​
    private static boolean isMyType(String typeName){
        return !isNotMyType(typeName);
    }
​
    //执行复杂查询,返回例如统计结果
    protected Object[] executeComplexQuery(String sql , Object... params){
        conn = getConn() ;
        try{
            psmt = conn.prepareStatement(sql);
            setParams(psmt,params);
            rs = psmt.executeQuery();
​
            //通过rs可以获取结果集的元数据
            //元数据:描述结果集数据的数据 , 简单讲,就是这个结果集有哪些列,什么类型等等
​
            ResultSetMetaData rsmd = rs.getMetaData();
            //获取结果集的列数
            int columnCount = rsmd.getColumnCount();
            Object[] columnValueArr = new Object[columnCount];
            //6.解析rs
            if(rs.next()){
                for(int i = 0 ; i<columnCount;i++){
                    Object columnValue = rs.getObject(i+1);     //33    苹果      5
                    columnValueArr[i]=columnValue;
                }
                return columnValueArr ;
            }
        }catch(SQLException e){
            e.printStackTrace();
            throw new DAOException("BaseDAO executeComplexQuery出错了");
        }
​
        return null ;
    }
​
    //执行查询,返回单个实体对象
    protected T load(String sql , Object... params){
        conn = getConn() ;
        try{
            psmt = conn.prepareStatement(sql);
            setParams(psmt,params);
            rs = psmt.executeQuery();
​
            //通过rs可以获取结果集的元数据
            //元数据:描述结果集数据的数据 , 简单讲,就是这个结果集有哪些列,什么类型等等
​
            ResultSetMetaData rsmd = rs.getMetaData();
            //获取结果集的列数
            int columnCount = rsmd.getColumnCount();
            //6.解析rs
            if(rs.next()){
                T entity = (T)entityClass.newInstance();
​
                for(int i = 0 ; i<columnCount;i++){
                    String columnName = rsmd.getColumnName(i+1);            //fid   fname   price
                    Object columnValue = rs.getObject(i+1);     //33    苹果      5
                    setValue(entity,columnName,columnValue);
                }
                return entity ;
            }
        }catch (Exception e){
            e.printStackTrace();
            throw new DAOException("BaseDAO load出错了");
        }
​
        return null ;
    }
​
    //执行查询,返回List
    protected List<T> executeQuery(String sql , Object... params){
        List<T> list = new ArrayList<>();
        conn = getConn() ;
        try{
            psmt = conn.prepareStatement(sql);
            setParams(psmt,params);
            rs = psmt.executeQuery();
​
            //通过rs可以获取结果集的元数据
            //元数据:描述结果集数据的数据 , 简单讲,就是这个结果集有哪些列,什么类型等等
​
            ResultSetMetaData rsmd = rs.getMetaData();
            //获取结果集的列数
            int columnCount = rsmd.getColumnCount();
            //6.解析rs
            while(rs.next()){
                T entity = (T)entityClass.newInstance();
​
                for(int i = 0 ; i<columnCount;i++){
                    String columnName = rsmd.getColumnLabel(i+1);            //fid   fname   price
                    Object columnValue = rs.getObject(i+1);     //33    苹果      5
                    setValue(entity,columnName,columnValue);
                }
                list.add(entity);
            }
        }catch (Exception e){
            e.printStackTrace();
            throw new DAOException("BaseDAO executeQuery出错了");
        }
        return list ;
    }
}

Service

创建各类服务层,调用各类DAO实现类,用其中的方法完成各类业务操作。

以UserBasicService为例,完成登录和通过特定用户信息查询其好友列表

UserBasicService接口

package com.atguigu.qqzone.service;
​
import com.atguigu.qqzone.dao.UserBasicDAO;
import com.atguigu.qqzone.pojo.UserBasic;
​
import java.util.List;
​
public interface UserBasicService {
    UserBasic login(String loginId , String pwd );
    List<UserBasic> getFriendList(UserBasic userBasic);
}

UserBasicService实现类

package com.atguigu.qqzone.service.impl;
​
import com.atguigu.qqzone.dao.UserBasicDAO;
import com.atguigu.qqzone.pojo.UserBasic;
import com.atguigu.qqzone.service.UserBasicService;
​
import java.util.ArrayList;
import java.util.List;
​
public class UserBasicServiceImpl implements UserBasicService {
​
    private UserBasicDAO userBasicDAO = null ;
​
    @Override
    public UserBasic login(String loginId, String pwd) {
        UserBasic userBasic = userBasicDAO.getUserBasic(loginId,pwd);
        return userBasic;
    }
​
    @Override
    public List<UserBasic> getFriendList(UserBasic userBasic) {
        List<UserBasic> userBasicList = userBasicDAO.getUserBasicList(userBasic);
        List<UserBasic> friendList = new ArrayList<>(userBasicList.size());
        for (int i = 0; i < userBasicList.size(); i++) {
            UserBasic friend = userBasicList.get(i);
            friend = userBasicDAO.getUserBasicById(friend.getId());
            friendList.add(friend);
        }
        return friendList;
    }
}

3.2 配置applicationContext.xml

通过依赖注入DI,在Tomcat启动时读取该配置文件,提前创建相应运行时service对象,根据配置文件中的bean,根据客户端发送的不同参数dispatcher中央控制器会使用不同的controller,调用不同的service来完成相应业务。

<?xml version="1.0" encoding="utf-8"?>
​
<!DOCTYPE beans [
    <!ELEMENT beans (bean*)>
    <!ELEMENT bean (property*)>
    <!ELEMENT property (#PCDATA)>
​
    <!ATTLIST bean id ID #REQUIRED>
    <!ATTLIST bean class CDATA #IMPLIED>
    <!ATTLIST property name CDATA #IMPLIED>
    <!ATTLIST property ref IDREF #IMPLIED>
]>
​
<beans>
    <bean id="userBasicDAO" class="com.atguigu.qqzone.dao.impl.UserBasicDAOImpl"/>
    <bean id="topicDAO" class="com.atguigu.qqzone.dao.impl.TopicDAOImpl"/>
​
    <bean id="userBasicService" class="com.atguigu.qqzone.service.impl.UserBasicServiceImpl">
        <property name="userBasicDAO" ref="userBasicDAO"/>
    </bean>
​
    <bean id="topicService" class="com.atguigu.qqzone.service.impl.TopicServiceImpl">
        <property name="topicDAO" ref="topicDAO"/>
    </bean>
​
    <bean id="user" class="com.atguigu.qqzone.controller.UserController">
        <property name="userBasicService" ref="userBasicService"/>
        <property name="topicService" ref="topicService"/>
    </bean>
    <bean id="page" class="com.atguigu.myssm.myspringmvc.PageController"/>
​
</beans>

XML 验证


拥有正确语法的 XML 被称为"形式良好"的 XML。

通过 DTD 验证的XML是"合法"的 XML。

<?xml version="1.0" encoding="utf-8"?>
​
<!DOCTYPE beans [
    <!ELEMENT beans (bean*)>
    <!ELEMENT bean (property*)>
    <!ELEMENT property (#PCDATA)>
​
    <!ATTLIST bean id ID #REQUIRED>
    <!ATTLIST bean class CDATA #IMPLIED>
    <!ATTLIST property name CDATA #IMPLIED>
    <!ATTLIST property ref IDREF #IMPLIED>
]>

3.3 修改login.html中的action

对登录页面,提交的是user.do,调用userBasicService、和topicService,经过thymeleaf渲染实现登录后页面。

th:action="@{/user.do}

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="css/common.css">
    <link rel="stylesheet" href="css/login.css">
</head>
<body>
    <div id="div0">
        <div id="div_container">
            <p class="center">用户登录1</p>
            <form th:action="@{/user.do}💥💥" method="get">
                <input type="hidden" name="operate" value="login"/>
                <table>
                    <tr>
                        <th>用户名:</th>
                        <td><input type="text" name="loginId" value="u001"/></td>
                    </tr>
                    <tr>
                        <th>密码:</th>
                        <td><input type="password" name="pwd" value="ok"/></td>
                    </tr>
                    <tr>
                        <th colspan="2">
                            <input type="submit" value="登录"/>
                            <input type="button" value="还没有账号?"/>
                        </th>
                    </tr>
                </table>
            </form>
        </div>
    </div>
</body>
</html>
posted @ 2022-08-15 22:18  Fancy[love]  阅读(283)  评论(0编辑  收藏  举报