这里还是这四个页面:
博客列表页
博客详情页
登录页
博客编辑页
一、准备工作:
1.引入依赖
引入mysql,servlet,jackson的依赖,并且把之前的前端页面拷贝进去.
2.创建目录
并且把相关代码复制进去.
此时目录就完成了!!!
3.复制前端代码
直接ctrl+v我们之前的前端代码到webapp目录即可!!!
二、设计数据库
结合我们的需求,我们可以发现在当前的博客实体中,主要设计两个实体:
博客,用户
所以我们要创建两个表:
设计表首先要确定:实体,关系
实体是用户和博客,关系是一对多(一个用户可以写多篇博客,而一片博客只能属于一个用户).
blog(blogId,title,content,postTime,useId)
user(userId,username,password)
三、数据库的创建
首先创建一个db.sql文件留存我们的数据库文件以观后用.
此时这两张表就创建好了!!!
接下来是数据库的封装操作:
public class DBUtil {
private static DataSource dataSource=new MysqlDataSource();
static {
((MysqlDataSource) dataSource).setUrl("jdbc:mysql://127.0.0.1/3306/blog_system?characterEncoding=utf8&useSSL=false");
((MysqlDataSource) dataSource).setUser("root");
((MysqlDataSource) dataSource).setPassword("111111");
}
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet){
if(resultSet!=null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(statement!=null){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(connection!=null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
之后就是创建实体类了(和表中的记录对应的类):
其实为的是把表中的数据提取出来,所以实体类的元素是和表的结构密切相关的.
之后就是进行数据库增删改查的封装了:
针对博客表,创建BlogDao.
针对用户表创建UserDao.
这个Dao就是Data Access Object的缩写,意思是这是一个用来访问数据的对象.
接下来就是"增删改查"的编写了:
首先是"增":
接下来是"查ID" :
接下来是"查全部":
//3.直接查询出数据库中所有的博客列表(用于博客列表页)
public List<Blog> selectAll(){
List<Blog> blogs=new ArrayList<>();
Connection connection=null;
PreparedStatement statement=null;
ResultSet resultSet=null;
try {
//1.与数据库建立连接
connection=DBUtil.getConnection();
//2.构造sql语句
String sql="select * from blog";
statement=connection.prepareStatement(sql);
//3.执行sql语句
resultSet=statement.executeQuery();
//4.遍历结果
while(resultSet.next()){
Blog blog=new Blog();
blog.setBlogId(resultSet.getInt("blogId"));
blog.setTitle(resultSet.getString("title"));
//注意此处的content是不能在博客列表页中显示全部内容的.所以要判断长度,返回指定长度
String content=resultSet.getString("content");
if(content.length()>=100){
content=content.substring(1,100)+"...";
}
blog.setContent(content);
blog.setPostTime(resultSet.getTimestamp("postTime"));
blog.setUserId(resultSet.getInt("userId"));
blogs.add(blog);
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
DBUtil.close(connection,statement,resultSet);
}
return blogs;
}
最后就是"删除":
之后是UserDao的代码编写:
和上述几乎一致,不需要用脑.
此时我们的准备工作基本完成,接下来就是真正的后端代码的编写了:
首先是围绕"博客列表页"进行后端的编程:
当前博客列表页的内容都是写死的.正确的做法是通过数据库读取数据显示到页面上去
我们实现这个功能的基本思路就是:
让博客列表页在加载的时候,通过ajax给服务器发送一个请求.服务器在查数据库数据获取到博客列表数据,返回给浏览器浏览器再根据数据构造页面.
这样的交互过程,也被称为是"前后端分离",前端只向后端请求数据,而不请求页面的具体内容.后端也仅仅是返回数据.
这样设定的目的就是使前端和后端更加的"解耦合".
(之前的网站都是直接让服务器把一个完整的页面拼装好,然后把整个页面返回给浏览器(服务器渲染))
上述是实现博客列表页的基本思路!!!
接下来需要:
1.约定前后端交互接口
我们要确定前端要发什么内容,和后端要回应什么内容.
程序设计:
请求:GET/blog
响应:(使用json格式的数据来组织)(数组)
[
{
blogId:1,
title:"这是一篇博客",
content:"从今天开始我要开始写代码了!!!",
postTime:"2023-4-4 21:05:00",
userId:1
}
]
这是我们设计的传输数据的格式(当然使用其他的格式也是可以的!!!)
2.开发后端代码
首先创建BlogServlet类:
具体原理就是将存储的字符串格式转化为json格式然后传给前端网站.
有点乱整理一下目录:
3.开发前端代码
在博客列表页的加载过程中,触发ajax,访问服务器中的数据.
再把拿到的数据构造到页面中.
首先引入jquery:
因为之前在写前端程序的时候再文件夹中引入了jqueryMin所以现在我们可以直接引入文件夹的就可以了,不需要引入网络链接.
接下来就是依照之前的代码格式,将其以js的格式实现出来:
<script src="./js/jqueryMin.js"></script>
<script>
function getBlog(){
$.ajax({
type:'get',
url:'blog',
//注意这里是我们之前自己设置的接收json格式的数组,
//所以自然也需要循环读取每一个键值对
success:function(body){
for(let blog of body){
//构造整个博客div
let blogDiv=document.createElement('div');
blogDiv.className='blog';
//构造title的div
let titleDiv=document.createElement('div');
titleDiv.className='title';
titleDiv.innerHTML=blog.title;
blogDiv.appendChild(titleDiv);
//构造发布时间的div
let dateDiv=document.createElement('div');
dateDiv.className='date';
dateDiv.innerHTML=blog.postTime;
blogDiv.appendChild(dateDiv);
//构造全文摘要部分
let descDiv=document.createElement('div');
descDiv.className='desc';
descDiv.innerHTML=blog.content;
blogDiv.appendChild(descDiv);
//构造查看全文按钮
let a=document.createElement('a');
a.innerHTML='查看全文 > >';
//这里我们希望的是点击按钮之后实现页面之间的跳转
//但是这里我们还是需要把用户信息给传到博客详情页去.
a.href='blogDetail.html?blogId='+blog.blogId;
blogDiv.appendChild(a);
}
}
});
}
</script>
之后还有一步将我们现在写的代码挂到父类之上:
注意:此处以后我们学习了框架之后会大量的简化代码.
此时我们的博客列表页也就写的差不多了!!!
启动Tomcat看看效果
此时没有东西是正常的,因为此时数据库没有数据!!
如果想要测试可以自行添加数据:
另外,此时的博客列表页的发布时间是由bug的,所以我们要进行发布时间返回值的重构(这个直接搜一下就有):
此时博客列表页也就完成了!!!
接下来就是实现博客详情页的功能:
具体流程:
点击"查看全文"按钮,就能跳转到博客详情页中去,跳转过去后在博客详情页发起一个ajax标签,从服务器获取到当前博客的内容.
1.约定前端后端接口
请求:
GET/blog?blogId=1
这里的blog路径和博客列表页是同一个,此处是不是同一个其实都可以,约定成同一个就直接在BlogServlet中进行修改.约定成不同路径,就创建新类然后重新写一个.
注意:这里的请求中是有query string的,因为需要去索取指定博客id.
响应:
HTTP/1.1 200 OK
{
blogId:1,
title:"这是第一篇博客",
content:"从今天起我要开始写博客了!",
postTime:"2023-04-20:00:00",
userId:1
}
此时请求与响应的格式就已经约定完成了.接下来我们要进行后端代码的编写了:
2.实现后端代码
实现是通过queryString过去blogId,然后尝试去判断blogId
我们只需要使用blogId获取selectById中的blog就可以实现博客的获取了:
注意:此处的getParameter返回的不是一个整数,而是一个字符串,所以我们这里要首先将其转换为一个整数.
此处我们获取到了blog,现在就需要把这个blog使用jackson转换为json格式然后再发给前端:
此时后端的代码也就搞定了!!!
3.实现前端代码
首先我们删除之前所编写的所有固定内容:
接下来,就是在BlogDetail.html中加入ajax,来获取上述数据.
注意理解这里的location.search
这个 location.search是用来获取queryString的.
而这个queryString是来自我们的博客列表页:
此时,我们的服务器就可以得知blogId了,就可以指定blogId进行博客的搜索了!!!
接下来是页面中一些标签的设定:
首先是标题与日期更新:
然后是博客正文更新:
注意一点:这里的博客是使用markdown编译器进行的编译,而markdown中有很多类似' # '之类的有特殊意义的字符,而我们想在博客详情页中展示的是右边经过熏染完成了的部分,所以这里我们要借助editor.md中提供的editormd.markdownToHTML这个方法把md字符串转化为html片段.
这里我们的editormd.markdownToHTML只能识别id选择器,其他的它看不懂!!!
在使用方法之前我们还要引入mditor.md的依赖:
然后就是博客正文部分的更新:
此处就是完成了一个格式的转换!!
此时我们的博客详情页的前端+后端其实就写完了,但当我们运行的时候:
发现有bug 此处开始改bug
首先是它:
这个favicon.ico其实就是浏览器的图标
就是这个东西,因为我们这里没有设置,所以无伤大雅,它访问不到很正常!!!
这里推荐一个阿里的图标库:iconfont-阿里巴巴矢量图标库
之后就是它了:
点进去发现,其实就是body写成了blog
修改完成后,我们发现标题没出来:
这里我们发现我们的博客题目和title不太对劲,所以看看前端代码:
我们发现导航栏和标题都是"title"所以才出现了问题.
只要让这个选择器更加精确一点:
这样就大功告成了!!!
接下来就是登录页的实现了:
此处的输入用户名和密码点击登录,就会触发一个http请求.服务器验证用户名和密码,然后就可以根据结果,判断是否登录成功.
现在只是一个单纯的输入框,需要改成from表单才能进行信息的提交.
1.前后端交互接口
请求:
POST/login
username=zhangsan&password=123
响应:
HTTP/1.1 302
Location:blogList.html
注意:这里需要使用form表单的形式然后返回302才能进行页面的跳转.
2.修改前端代码
我们的任务是加上form表单,让数据能够进行交互.
以上是修改的内容
此时使用fiddler进行抓包,发现此时的请求和我们要求的一致.
3.修改后端代码
此处就需要一个servlet来处理登录请求.
package api;
import model.User;
import model.UserDao;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
/**
* Created with IntelliJ IDEA.
* Description:
* User: 86139
* Date: 2023-04-12
* Time: 16:03
*/
@WebServlet("/login")
public class LogInServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//此处是告诉servlet以怎样的格式去理解请求
req.setCharacterEncoding("utf8");
//此处是告诉servlet以怎样的格式来构造响应
// resp.setCharacterEncoding("utf8");
resp.setContentType("text/html;charset=utf8");
//1.读取参数中的用户名和密码
String username=req.getParameter("username");
String password=req.getParameter("password");
if(username==null || "".equals(username) || password == null || "".equals(password)){
String html="<h3>登录失败!缺少 username 或者 password 字段</h3>";
resp.getWriter().write(html);
return;
}
//2.读一下数据库看一下,用户名是否存在,并且密码是否匹配
UserDao userDao=new UserDao();
User user=userDao.selectByUsername(username);
if(user == null){
String html="<h3>登录失败,用户名或者密码错误!!!</h3>";
resp.getWriter().write(html);
return;
}
if(!password.equals(user.getPassword())){
String html="<h3>登录失败,用户名或者密码错误!!!</h3>";
resp.getWriter().write(html);
return;
}
//3.用户名密码验证通过,登陆成功,接下来创建会话,使用该会话保存用户信息.
HttpSession session=req.getSession(true);
session.setAttribute("user",user);
//4.进行重定向,跳转到博客列表页
resp.sendRedirect("blogList.html");
}
}
这段代码和之前的登录案例几乎一致,过多的就不再描述了.
注意如果此时尝试登录的话是无法成功的,因为数据库中压根就没有你的数据,所以要先添加数据:
此时就正常登录了!!!
之后我们来实现强制要求登录功能:
当用户访问 博客列表页/详情页/编辑页的时候,要求用户必须是已经登录的状态.
如果用户还没登录,就会强制跳转到登录页面.
实现思路:
在页面加载的时候,专门发起一个新的ajax(一个页面可以发N个ajax请求)
如果是在博客列表页
会先发起一个请求获取博客列表页,再发一个ajax获取用户的登录状态,如果此时用户已经登录,相安无事,如果未登录,则跳转到页面登录页.
1.约定前后端交互接口
请求:
GET/login
响应:
HTTP/1.1 200 OK
{
userId:1,
username:'zhangsan'
}
2.实现后端代码
注意:此处的存与取是遥相呼应的.是因为之前存了,所以这里才能取,就是把这个session当做了一个map,存放键值对,此处是在取键值对.
此时后端代码就完成了!!!
3.实现前端代码
注意:此处的location.assign()就是属于一个前端的强制跳转方法.
此处就相当于一个简单的强制跳转的代码格式.需要把这段代码粘贴到个页面,之后就大功告成了!!!
此时如果我们重新启动服务器,然后访问博客列表页,就会自动跳转到登录页面:
重启服务器就会重置所有的sessionId
之后就是用户信息部分的编写了:
也就是这一部分,要实现两个功能:
1.如果是博客列表页,此处显示登录用户的信息.
2.如果是博客详情页,此处显示的是该文章的作者.
还是老三套:
1.约定前后端交互接口(这里可以直接复用检测登录状态的接口)
博客列表页:
请求:
GET/login
响应:
HTTP/1.1 200 OK
{
userId:1,
username:'zhangsan',
password:'123'
}
博客详情页:
请求:
GET/author?blogId=1
响应:
HTTP/1.1 200 OK
{
userId:1,
username:'zhangsan',
password:'123'
}
2.修改前端代码
利用选择器找到指定标签,然后实现文件内容的修改.
之后就是博客详情页的用户信息了:
在这里抓住一点,就是什么时候要写一个新的servlet呢?
每个servlet都是绑定一个路径的,什么时候需要新写一个访问路径的时候,就多写一个servlet.
1.修改后端代码:
2.修改前端代码
就是根据选择器选中名字,然后根据收到的数据修改用户姓名.
我们可以看到,现在是以lisi的账号进去的
但是查询的确实zhangsan的博客:
之后就是注销功能了:
这里的注销当然不是消除账户了,只是退出登录
判断登录状态:
1.看是否能查到http session对象.
2.看session对象里面有没有user
这里的注销其实就是:
删除其中任何一个都是注销了.
但是HTTPsession对象想要删除,很难,getSession只有过去和创建会话,但不能删除.
所以这里我们选择把user干掉.使用removeAttribute
还是三板斧
1.约定前后端接口
请求:
GET/blogOut
响应:
HTTP/1.1 302
location:blogIn.html
2.修改后端代码
其实就是一个删除逻辑,尝试获取会话,如果没有就直接显示未登录,如果有就删除然后跳转到登录页面.
3.修改前端代码
其实就是改一下导航栏的链接
此时注销功能也就完成了:
最后一个功能就是发布博客:
主要还是博客编辑页的编写
此时写好一片博客后,点击发布博客,没有任何效果.依旧是三板斧
1.约定前后端交互接口
这里我们依旧使用form表单进行实现
首先我们要明确:
一片博客
blogId 自增主键,自己生成
title
content
userId 作者信息,可以从提交博客的人获取
postTime 当前时刻
所以说需要前后端交互的信息只有title与content.
请求:
POST/blog
title=这是标题&content=这个是正文
响应:
HTTP/1.1 302
Location:blogList.html
2.编写服务器代码
此处的的代码在blogservlet里进行编写用blog统一调用.
看着挺长其实就是根据逻辑进行编写.
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//实现发布博客功能
//读取请求,构造 Blog 对象,插入数据库中即可
HttpSession httpSession=req.getSession(false);
if(httpSession == null){
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("当前未登录,无法发布博客!!!");
return;
}
User user=(User) httpSession.getAttribute("user");
if(user == null){
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("当前未登录,无法发布博客!!!");
return;
}
//确保登录之后,就可以把作者给拿到了
//获取博客标题和正文
String title=req.getParameter("title");
String content=req.getParameter("content");
if(title == null || "".equals(title) || content == null || "".equals(content)){
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("当前提交数据有误! 标题或正文部分为空!");
return;
}
//构造 blog 对象
Blog blog=new Blog();
blog.setTitle(title);
blog.setContent(content);
blog.setUserId(user.getUserId());
//当前发布时间我们可以在Java中生成也可以在数据库中生成
blog.setPostTime(new Timestamp(System.currentTimeMillis()));
//插入数据库库
BlogDao blogDao=new BlogDao();
blogDao.add(blog);
resp.sendRedirect("blogList.html");
}
3.编写客户端代码
把页面改造一下,能够构造出请求并且发送即可.
这是我们对前端博客编辑页的修改,1.加上form标签 2.加上name属性 3.把button改成input标签type submit 4.textarea这是editor.md对于form表单要求的写法(其功能就是把用户输入的markdown 内容自动的放入一个隐藏的textarea里面)这样后续点击submit就能够自动提交了
这里的display: none就是把内容隐藏不显示出来.
此时启动服务器:
发现此时是有bug的
发开开发者工具,发现此时控制页面大小的是
此处的编写代码页大小使用的是100%-50px,而这里的100%是其父元素的100%.
但加了form后,是代码编写块的父元素成为了form,所以才会出现此bug.将form标签的大小也设置成100%就可以了!!!
此时就OK了!!!
此时随便发布一篇博客,点击提交:
发现乱码了!!!
这里的乱码一定是字符集的问题导致的,所以我们此时可以看看数据库中存放的内容,如果也是乱码,那么就是服务器读取的时候出现了问题:
此时就是正常的了!!!
此时博客系统就完成了!!!
当然还可以扩展很多其他的功能:
1.上传头像
2.删除博客
3.修改博客
......
该讲得东西都讲过了,之后想要继续扩展也可以自行进行实现了!!!