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 物理结构设计

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

3 系统代码实现

3.1 pojo、DAO、service

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

pojo

将数据库的表转化为pojo

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

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

DAO

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

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

创建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 ;
   }
}

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

Service

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

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

以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}

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/280802.html

(0)
上一篇 2022年8月16日
下一篇 2022年8月16日

相关推荐

发表回复

登录后才能评论