数据库的设计:
设计好对应的表结构,把数据库相关的代码封装起来。
a)找到实体:
博客(blog表)
用户(user表)
b)确认实体之间的关系
一对多
一个博客属于一个用户,一个用户可以发多个博客。
所以在blog表里,应该有userId这一属性列
用户表:
博客表:
指令如下:
create database if not exists blog_system charset utf8;use blog_system;drop table if exists blog;
drop table if exists user;create table blog (//主键blogId int primary key auto_increment,title varchar(1024),content varchar(4096),postTime datetime,userId int
);create table user (//主键userId int primary key auto_increment,//unique 唯一username varchar(50) unique,password varchar(50)
)insert into blog values(1,'第一篇博客','我要好好写代码',now(),1);
insert into blog values (2,'第二篇博客','我要好好写代码',now(),1);
insert into blog values (3,'第三篇博客','我要好好写代码',now(),1);insert into user values (1,'zhangsan','123');
insert into user values (2,'lisi','234');
采用MVC结构:
M(model):操作数据的代码
V(view):操作/构造页面的代码
C(controller):业务逻辑。处理前端的请求
model
DBUtil:
package model;import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;public class DBUtil {//通过这个类,封装数据库建立连接和释放资源的操作//因为多个servlet都需要使用到数据库,所以需要一个单独的地方来封装datasourceprivate static volatile DataSource dataSource = null;//创建数据源//此处使用单例模式,当tomcat收到多个请求时,不会出现线程不安全的情况。private static DataSource getDataSource() {if(dataSource == null) {synchronized(DBUtil.class) {if(dataSource == null) {dataSource = new MysqlDataSource();((MysqlDataSource) dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/blogSystem?characterEncoding=utf8&useSSl=false");((MysqlDataSource) dataSource).setUser("root");((MysqlDataSource) dataSource).setPassword("1234");}}}return dataSource;}//建立连接public static Connection getConnection() throws SQLException {return getDataSource().getConnection();}//释放资源public static void close(Connection connection, PreparedStatement preparedStatement, ResultSet resultSet) {//这里采用一个个回收,是防止当一个出现问题时,其它正常的依然能回收,不会堵住。if(resultSet != null) {try {resultSet.close();} catch (SQLException e) {throw new RuntimeException(e);}}if(preparedStatement != null) {try {preparedStatement.close();} catch (SQLException e) {throw new RuntimeException(e);}}if(connection != null) {try {connection.close();} catch (SQLException e) {throw new RuntimeException(e);}}}
}
Blog:
package model;import java.sql.Timestamp;//创建实体类
//每一个表都需要一个专门的类来表示
//数据库里的每一条数据,都对应到这个类的一个对象
//Blog对象对应到blog表里的一条记录
//表里有哪些列,这个类里就应该有哪些属性
public class Blog {private int blogId;private String title;private String content;private Timestamp postTime;private int userId;public int getBlogId(){return blogId;}public void setBlogId(int blogId){this.blogId = blogId;}public String getTitle(){return title;}public void setTitle(String title){this.title = title;}public String getContent(){return content;}public void setContent(String content){this.content = content;}public Timestamp getPostTime(){return postTime;}public void setPostTime(Timestamp postTime){this.postTime = postTime;}public int getUserId(){return userId;}public void setUserId(int userId){this.userId = userId;}@Overridepublic String toString() {return "Blog{" +"blogId=" + blogId +", title='" + title + '\'' +", content='" + content + '\'' +", postTime=" + postTime +", userId=" + userId +'}';}}
User:
package model;public class User {private int userId;private String username;private String password;public int getUserId() {return userId;}public void setUserId(int userId) {this.userId = userId;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}@Overridepublic String toString() {return "User{" +"userId=" + userId +", username=" + username +", password='" + password + '\'' +'}';}}
BlogDAO:
package model;//这个类主要是针对博客表的增删改查
//DAO:Data Access Project 数据访问对象,通过类的对象,完成对数据库表的操作import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;public class BlogDAO {//新增public void insert(Blog blog) {Connection connection = null;PreparedStatement preparedStatement = null;try {//1.建立连接connection = DBUtil.getConnection();//2.构造sql语句String sql = "insert into blog values(null,?,?,now(),?)";preparedStatement = connection.prepareStatement(sql);preparedStatement.setString(1, blog.getTitle());preparedStatement.setString(2,blog.getContent());preparedStatement.setInt(3,blog.getUserId());//3.执行sql语句preparedStatement.executeUpdate();} catch (SQLException e) {throw new RuntimeException(e);}finally{//4.最后释放资源DBUtil.close(connection,preparedStatement,null);}}//查询所有博客public List<Blog> getBlogs() {Connection connection = null;PreparedStatement preparedStatement = null;ResultSet resultSet = null;//将查询到的博客放在链表当中List<Blog> blogList = new ArrayList<>();try {//1.建立连接connection = DBUtil.getConnection();//2.创建sql语句//按照发布的时间查询,先发布的在下面String sql = "select *from blog order by postTime desc";preparedStatement = connection.prepareStatement(sql);//3.执行sql语句resultSet = preparedStatement.executeQuery();//用一个循环来遍历结果while(resultSet.next()) {Blog blog = new Blog();blog.setBlogId(resultSet.getInt("blogId"));blog.setTitle(resultSet.getString("title"));//对全文内容进行截取String content = resultSet.getString("content");if(content.length() > 100) {content = content.substring(0,100)+"...";}blog.setContent(content);blog.setPostTime(resultSet.getTimestamp("postTime"));blog.setUserId(resultSet.getInt("userId"));blogList.add(blog);}} catch (SQLException e) {throw new RuntimeException(e);}finally{//释放资源DBUtil.close(connection,preparedStatement,resultSet);}return blogList;}//查看全文,查询某一篇具体的博客public Blog getBlogById(int blogId) {Connection connection = null;PreparedStatement preparedStatement = null;ResultSet resultSet = null;try {//1.建立连接connection = DBUtil.getConnection();//2.构造sql语句String sql = "select *from blog where blogId = ?";preparedStatement = connection.prepareStatement(sql);preparedStatement.setInt(1,blogId);preparedStatement.executeQuery();//此处只有一个查询结果,不需要用循环if(resultSet.next()) {Blog blog = new Blog();blog.setBlogId(resultSet.getInt("blogId"));blog.setTitle(resultSet.getString("title"));blog.setContent(resultSet.getString("content"));blog.setPostTime(resultSet.getTimestamp("postTime"));blog.setUserId(resultSet.getInt("userId"));return blog;}} catch (SQLException e) {throw new RuntimeException(e);}finally {//释放资源DBUtil.close(connection,preparedStatement,resultSet);}return null;}//删除某篇博客public void delete(int blogId) {Connection connection = null;PreparedStatement preparedStatement = null;try {//1.建立连接connection = DBUtil.getConnection();//2.构造sql语句String sql = "delete from blog where blogId = ?";preparedStatement = connection.prepareStatement(sql);preparedStatement.setInt(1, blogId);//3.执行sql语句preparedStatement.executeUpdate();} catch (SQLException e) {throw new RuntimeException(e);} finally {DBUtil.close(connection, preparedStatement, null);}}
}
UserDAO:
package model;import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;public class UserDAO {//根据userId获取到用户的信息public User getUserById(int userId) {Connection connection = null;PreparedStatement preparedStatement = null;ResultSet resultSet = null;try {//1.建立连接connection = DBUtil.getConnection();//2.构造sql语句String sql = "select *from user where userId = ?";preparedStatement = connection.prepareStatement(sql);preparedStatement.setInt(1,userId);resultSet = preparedStatement.executeQuery();if(resultSet.next()) {User user = new User();user.setUserId(resultSet.getInt("userId"));user.setUsername(resultSet.getString("username"));user.setPassword(resultSet.getString("password"));return user;}} catch (SQLException e) {throw new RuntimeException(e);}finally{DBUtil.close(connection,preparedStatement,resultSet);}return null;}//根据username获取到用户的信息public User getUserByName(String userName) {Connection connection = null;PreparedStatement preparedStatement = null;ResultSet resultSet = null;try {//1.建立连接connection = DBUtil.getConnection();//2.构造sql语句String sql = "select *from user where username = ?";preparedStatement = connection.prepareStatement(sql);preparedStatement.setString(1,userName);resultSet = preparedStatement.executeQuery();if(resultSet.next()) {User user = new User();user.setUserId(resultSet.getInt("userId"));user.setUsername(resultSet.getString("username"));user.setPassword(resultSet.getString("password"));return user;}} catch (SQLException e) {throw new RuntimeException(e);}finally{DBUtil.close(connection,preparedStatement,resultSet);}return null;}
}
后面会有一些数据库的框架:MyBatis,MyBatisPlus
用来封装JDBC的代码,本质上就是自动生成JDBC的代码
1.获取博客列表页
在博客列表页加载的时候,通过ajax给服务器发送请求,从数据库拿到博客列表数据,显示到页面上。
1)约定前后端交互的接口
2)让浏览器给服务器发送请求
3)服务器处理上述请求
4)让前端代码处理上述响应的数据
构成html片段,显示到页面上
前端页面生成页面的方式有很多种,这里采用的是基于dom api(document object model)的方式
dom api是浏览器提供的标准的api(不属于任何的第三方框架和库)
类似于jdbc api
前端有一些框架和库
把dom和api进行了封装,用起来更简便
1. querySelector:获取页面已有的元素
2. createElement:创建新的元素
3. .innerHtml:设置元素里的内容
4. .className:设置元素的class属性
5. appendChild: 把这个元素添加到另一个元素的末尾
html中显示 > 需要使用到>;
显示 < 需要使用到<;
html中的<>就是用这个构成
当前代码存在的两个问题:
1.
这里的发布时间是时间戳,我们需要格式化时间
这样就行:
2.
2.博客详情页
1)约定前后端交互的接口
2)让前端代码通过ajax发送请求
我们发送请求的时候需要带有blogId,而blogId处于博客详情页的url中,
我们可以通过location.search拿到页面url中的query string
一个路径对应到一个servlet
我们使用的一个servlet处理两个请求
博客列表页不带query string
博客详情页带有query string
可以根据是否有query string来区分是哪种请求,返回不同的数据
(使用两个servlet也可以,只是需要设置不同路径)
3)让服务器处理请求
4.前端拿到响应之后,把响应的数据,构造成页面的html片段
5.虽然正文的内容已经显示出来(正文的md原始数据),但是博客网站,应该显示的是md渲染后的效果
通过第三方库(editor.md)可以实现:
class属性往往是与css搭配。id则是一个身份标识,并且是唯一的。
editormd是一个全局变量,依赖正确引入,变量直接使用。
把blog.content这里的md原始数据,渲染成html,放到id为content的div中
3.实现登录
在登录页面,在输入框中填写用户名和密码
点击登录,就会给服务器发起HTTP请求(可以使用ajax,也可以使用form)
服务器处理登录请求。读取用户名和密码。
在数据库中查询匹配,如果正确就登陆成功,创建会话,跳转到博客列表页(这里采用重定向)。
1)约定前后端交互的接口
2)让前端发送请求
3)让服务器处理请求
4.实现强制登录
在博客列表页,详情页,编辑页,判定当前用户是否已经登陆
如果没有登录,则强制跳转到登录页(登录之后才可以使用)
1)约定前后端交互的接口
2)前端发起请求
一个页面可以触发多个ajax请求,不会互相干预
因为我们想要让博客列表页,博客编辑页,博客详情页都有强制登陆
所以我们可以把一些公共的js代码单独提取出来,放到某个.js文件中
然后通过html中的script标签,来引用文件的内容
eg:
博客列表页的强制登陆:
博客详情页的强制登录:
3)服务器处理请求:
虽然登陆过了,一旦重新启动服务器
就会被判定为未登录状态
登陆状态是通过服务器这里的session存储的
session这是服务器内存中的类似于hashmap这样的结构
一旦服务器重启了,hashmap原有的内容就没了
有更好的解决方案:
1.可以将会话进行持久化保存(文件,数据库,redis)
可以使用令牌的方式(把用户信息在服务器加密,还是保存在浏览器这边),相当于服务器没有在内存中存储当前用户的身份。
5.显示用户信息
博客列表页:显示的是当前登录的用户的信息
博客详情页:显示的是当前文章的作者信息
页面加载的时候,给服务器发送ajax请求,服务器返回对应的数据
1)约定前后端交互的接口
博客列表页:
博客详情页:
2)前端发送请求:
博客列表页:
博客详情页:
3)服务器处理请求:
博客列表页:
博客详情页:
这里我们查询分为了两步,先查blog表里的blog对象,再查user表
实际上可以使用联合查询,把blog user进行笛卡尔积,找出匹配的结果(可是开销大)
也可以使用子查询,把两个sql合并成一个(可读性查)
4)前端处理响应
6.退出登录
当点击注销(a标签,可有一个href属性)的时候,触发一个HTTP请求(GET),可能会引起浏览器的跳转
服务器收到GET请求时,就会把会话里的user这个Attribute给删掉
因为判断用户是否登录时,需要同时验证:会话存在,user的Attribute也许需要存在
破坏一个就可以使登陆状态发生改变
为什么不直接删除掉session?
:servlet没有提供删除session的方法
(虽然可以给session设置过期时间,起到删除的时间)
1)约定前后端交互的接口
2)前端发送请求:
3)后端处理请求:
7.发布博客
提交数据(title和content)
服务器存入数据库
1)约定前后端交互的接口
请求:
这里使用ajax,或者form(填写输入框,提交数据,使用form更方便)
2)前端发送请求
editor md 和form表单配合使用
初始化editor md 的编辑器的代码
3)服务器处理请求
出现问题时,如何解决?
比如:进入博客列表页,发现页面中的数据没有加载,该咋办?
抓包!!!
需要明确在点击刷新的过程中,浏览器个服务器之间有几次http交互(也就是前后端交互的接口)
之后就是观察抓包结果中,http交互的请求都是否符合预期
1.先看请求发没发
比如ajax写的方法是否调用了
2.再看请求中的各个部分对不对,是否符合约定的接口要求。
不符合则是前端代码的问题
3.请求没问题,看响应的数据
是否返回?数据库的语句,数据库的连接,服务器控制台是否异常
4.请求和响应都没问题,但是未显示。
前端处理响应,可以F12看控制台是否报错。
确定范围之后,进一步排查问题,加入更多日志。
System.out.println
Console.log