每天学习多一点!烦恼少一点!
当JavaWeb知识学习结束之后,通过一个项目让我们能够更好更扎实的理解和掌握知识!!
我将我学习的过程编写为一篇文章,能够让每个初学者小伙伴有一个更好的学习。
文章目录
项目介绍
我们通过前后端分离的技术来实现。
前端通过我们所学三剑客:HTML+CSS+JS来编写我们博客系统所需要的页面、通过ajax和form表单来实现浏览器和Web服务器之间的数据传输。
后端通过Servet+Tomcat+JDBC+MySQL来实现博客系统后端主要逻辑的实现。
博客系统中主要包含:
前端实现四个页面:博客登录页、博客列表页、博客详情页、博客编辑页。(详情见代码,这里就不进行详细讲解了)
后端实现八个功能:实现博客列表页的展示功能、实现博客详情页的展示功能、登录功能、限制用户权限、显示用户权限、退出登录、发布博客、删除博客。
项目实现
1.项目创建
1.1 创建一个Maveni项目
1.2 引入依赖
通过maven将代码中所需要依赖的jar包添加到项目中
Servlet、mysql、jackson
将依赖添加到pom.xml文件中
<dependencies>
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.4</version>
</dependency>
</dependencies>
1.3 创建项目所用到的目录
如下图:在WEB-INF目录下创建web.xml文件
并且在文件中添加如下代码:
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
</web-app>
2.设计项目所需要的数据库
项目中我们会用到两个表:
博客表blog(blogId title content postTime userId)
用户表user (userId username password);
我们将数据库建表语句写到一个文件中,之间进行粘贴复制,方便我们使用:
create database if not exists blog_system;
use blog_system;
drop table if exists blog;
create table blog (
blogId int primary key auto_increment,
title varchar(1024), -- 博客标题
content mediumtext, -- 博客正文
userId int, -- 作者的 id
postTime datetime -- 发布时间
);
drop table if exists user;
create table user (
userId int primary key auto_increment,
username varchar(128) unique,
password varchar(128)
);
insert into blog values(null,"这是第一篇博客","认真学Java",1,'2022-12-13');
insert into blog values(null,"这是第一篇博客","认真学Java",2,'2022-12-13');
insert into user values(null,"admin","123");
insert into user values(null,"Jack","123");
设计的关键要素:我们要找到数据库中所包含的“实体”
auto_increment:主键自增长 数据库系统根据定义自动赋值。每增加一条记录,主键会自动以相同的步长进行增长。
我们将构建好的语句直接粘贴到Mysql中,在我们的数据库中存好信息
查询数据库:
查询表:
3.封装数据库操作
在java文件夹下创建model文件夹,我们将操作数据库所用到的代码都存放到这个文件夹中
我们先将数据库建立和断开操作的代码进行封装,这样以后我们在使用的时候比较方便
在model文件夹中创建DBUtil类
我们这里的编写的是一个懒汉模式的单例。所以要考虑线程安全问题~~
Servlet 程序是运行在多线程环境中,每个请求可能对应着一个线程,Tomcat也是通过多线程的方式来处理请求
然后我们将用到的增删改查也进行封装
3.1 Blog类和User类
先创建一个Blog类,表示一篇博客
创建一个User类,表示一个用户
3.2 BlogDao类和UserDao类
然后再创建两个类:BlogDao、UserDao。这两个类主要包对博客表和用户表的增删改查操作
在BlogDao类中我们要实现以下几点功能:
1.新增博客(博客编辑页)
2.查询出博客列表(博客列表页)
3.查询出指定博客的详细内容(博客详情页)
4.删除指定的博客
这四步操作就代表的上述所要实现的四个功能,对于四个功能的详细编写,均为JDBC操作,这里只展示一个方法的实现。这里当我们把所有的都写完之后会发现,同质化很严重。网上查了之后发现有一种方便操作的框架–MyBatis(数据库操作框架)这个只有制定好SQL语句,框架会自动生成JDBC代码。(这个还没学0-0)
public void insert(Blog blog) {
Connection connection = null;
PreparedStatement statement = null;
try {
// 1. 先和数据库建立连接
connection = DBUtil.getConnection();
// 2. 构造 SQL 语句
String sql = "insert into blog values(null, ?, ?, ?, now())";
statement = connection.prepareStatement(sql);
statement.setString(1, blog.getTitle());
statement.setString(2, blog.getContent());
statement.setInt(3, blog.getUserId());
// 3. 执行 SQL
int ret = statement.executeUpdate();
if (ret == 1) {
System.out.println("插入成功!");
} else {
System.out.println("插入失败!");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBUtil.close(connection, statement, null);
}
}
这里我们用到的setXXX方法,是从下标1开始,计算的是第几个,而不是第几列
所以 statement.setString(1, blog.getTitle()); 对应的就为查询语句中的第一个 ?
然后我们对照User表将UserDao类编写完成,这里主要涉及俩个操作:
1.当我们实现登录时,能够根据用户名实现查询
2.通过用户的Id来查询用户信息
这俩步操作就代表这要实现的俩个功能,这里也通过JDBC操作进行实现,只展示一个方法的具体实现
public User selectByName(String username) {
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
connection = DBUtil.getConnection();
String sql = "select * from user where username = ?";
statement = connection.prepareStatement(sql);
statement.setString(1, username);
resultSet = statement.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 throwables) {
throwables.printStackTrace();
} finally {
DBUtil.close(connection, statement, resultSet);
}
return null;
}
4.核心业务功能实现
我们先将设计好的前端代码粘贴到我们的项目中webapp文件夹下
然后通过配置Tomcat观察是否成功
配置成功后启动
如下图我们能够成功启动,说明文件没有错误。
4.1 博客列表页
这一模块关键工作为:在页面加载时候,我们通过ajax来访问服务器。然后从服务器拿到博客列表页所需要的详细情况,这个时候需要查询数据库,然后页面再将查询到的数据显示到界面中
我们通过下面三个步骤来完成上述功能的实现:
1.约定前后端交互所用接口(重点内容)
2.编写后端代码
3.编写前端代码
4.1.1 约定前后端交互所用接口(方法不唯一)
请求:博客列表页加载时给服务器发送的ajax请求
GET /blog
响应:服务器把博客列表数据返回给页面
Content-Type:application/json
> {
>
> {
>
> blogId:1,
>
> title:'第一篇博客'
>
> content:"今天起开始好好写代码“,
>
> userId:1,
>
> postTime:"2022-12-25 21:34:00
>
> },
>
> }
4.1.2 编写后端代码
在java下创建一个新的包:controller,我们将所用到的业务代码均放到这个包中
在包中创建一个BlogServlet 来实现GET /blog接口
4.1.3 编写前端代码
在前端我们需要在编写好的blog-list.html中进行修改
前后端交互需要引入Jquery
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
根据返回的 json 数据, 来构造出页面内容, jquery ajax 会自动的把响应得到的 body 按照响应的 Content-Type 进行转换格式. 如果响应的 Content-Type 是 json, 此时就会自动把 body 转成 js 的对象。这里的body就是我们约定前后端接口中{ }里面所包含的内容
编写完之后我们通过启动Tomcat,查看前端是否能够查询到我们数据库中所存的数据
这里有一个需要改动的地方就是:我们博客创建的时间
我们需要将后端Blog代码中 getPostTime() 这个方法进行魔改,这样返回的时间就能按照咱们所规定的进行返回。
public String getPostTime() {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return simpleDateFormat.format(this.postTime);
}
4.2 博客详情页
这一模块所要实现功能为:当我们点击查看详情时让页面跳转到博客详情页(blog-detail.html)然后在跳转的过程中,url带上当前获取到的博客Id。然后在blog-detail.html页面中,通过ajax从数据库中查询到博客详情内容,然后显示到界面中
我们还是通过下面三个步骤来完成上述功能的实现:
1.约定前后端交互所用接口
2.编写后端代码
3.编写前端代码
4.2.1 约定前后端交互所用接口
请求:
GET /blog?blogId=1
响应:
HTTP/1.1 200 OK
Content-Type:application/json
{
blogId:1,
title:'我的第一篇博客',
content:'这是正文',
userId:1,
postTime:'2022-12-26 10:00:00'
}
4.2.2 编写后端代码
因为代码中已经有一个 /blog 的 Servlet 了,就在之前的 Servlet 基础上做出修改即可,基于同一个 Servlet ,同一个doGet方法,让它既可以处理获取博客列表,又能获取博客详情。通过判断 /blog后边所带的参数是否存在来绝对返回的是播客列表还是博客详情!!
4.2.3 编写前端代码
在blog-list.html中做出如下修改,来实现页面的跳转
//构造查看全文按钮
let a = document.createElement('a');
a.href = 'blog_detail.html?blogId = ' + blog.blogId;
a.innerHTML = '查看全文 >>';
blogDiv.appendChild(a);
在blog-detail.html中加入ajax,发送请求给后端
这里有一个需要改动的地方:在返回博客详情数据时,我们添加了editor.md来进行渲染,让我们的博客能够以markdown类型在页面中输出
我们需要在blog_detail.html中添加editor.md依赖
<!-- 引入 editor.md 的依赖 -->
<link rel="stylesheet" href="editor.md/css/editormd.min.css"/>
<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="editor.md/lib/marked.min.js"></script>
<script src="editor.md/lib/prettify.min.js"></script>
<script src="editor.md/editormd.js"></script>`
编写完成之后我们再次重启Tomcat,观察我们前端页面是否成功
4.3 博客登录页
这一模块所要实现功能为:在登录页面中,输入用户名密码,在数据库中验证,完成登录功能。因为该项目暂时没有实现注册功能,所以测试所用账户和密码以提前存入到数据库中,具体数据请看数据库设计步骤。
我们还是通过下面三个步骤来完成上述功能的实现:
1.约定前后端交互所用接口
2.编写后端代码
3.编写前端代码
4.3.1 约定前后端交互所用接口
这里通过使用form表单来提交数据(ajax也可以,但是相比较还是form表单更为简单)
请求:
POST /login
Content-Type:application/x-www-form-urlencoded
Username=admin&password=123
响应:
HTTP/1.1 302 OK
/*如果登录成功,自动跳转到博客列表页。如果登录失败,则返回登录失败*/
Location:blog-list.html
4.3.2 编写后端代码
我们在controller包下创建一个LoginServlet 来处理请求
4.3.3 编写前端代码
将login.html进行一下修改:
1.添加form 2.给input添加name属性 3.将登录的type改成submit
编写完成之后,再次重新启动Tomcat,测试一下是否成功:
4.4 限制用户权限
在用户未登录时,用户无法实现直接进入到博客详情页和博客列表页。若用户未登录直接访问博客详情和列表页时,会强制用户跳转到登录页进行登录。
我们还是通过下面三个步骤来完成上述功能的实现:
1.约定前后端交互所用接口
2.编写后端代码
3.编写前端代码
4.4.1 约定前后端交互所用接口
请求:
GET /login
Cookie:JSESSIONID=xxxxxxx
响应:
HTTP/1.1 200 OK 【已登录】
HTTP/1.1 403 Forbidden 【未登录】
4.4.2 编写后端代码
在LoginServlet中加上这样一段代码:
4.4.3 编写前端代码
我们按照第一步所设计好的约定,然后通过ajax发送请求:
在blog-list.html中进行如下修改:
因为登录验证在多处调用,所以我们将这段代码写入到一个app.js文件中,然后直接引入这个文件就行
引入之后直接调用该方法即可:
4.5 左侧标签显示用户信息
如果是博客列表页,则左侧标签显示当前用户信息
如果是博客详情页,则左侧标签显示当前文章作者
我们还是通过下面三个步骤来完成上述功能的实现:
1.约定前后端交互所用接口
2.编写后端代码
3.编写前端代码
4.5.1 约定前后端交互所用接口
在博客列表页:发送一个ajax,获取到当前用户登录的用户信息,将这个用户信息显示到页面上即可
请求:
GET /userinfo
响应:
HTTP/1.1.200 OK
{
userid:1,
username:‘admin’(当前登录的用户)
}
在博客详情页,发送一个ajax并且带参数:blogId,获取到这个指定的blogId对应的作者信息,将这个信息显示到页面上
请求:
GET /userinfo?blogId=1
响应:
HTTP/1.1.200 OK
{
userid:1,
username:‘user’(当前文章的作者)
}
4.5.2 编写后端代码
在controller下创建一个UserInfoServlet
4.5.3 编写前端代码
在blog-list和blog-detail中添加如下代码
4.6 退出登录
这里我们要具体实现的功能是:清除当前用户的登录状态、跳转到博客登录页
我们通过下面三个步骤来完成上述功能的实现:
1.约定前后端交互所用接口
2.编写后端代码
3.编写前端代码
4.6.1 约定前后端交互所用接口
点击退出登录的时候,发送 一个get请求,并且跳转到博客登录页
请求:
GET /logout
响应:
HTTP/1.1 302
Location:login.html
4.6.2 编写后端代码
在controlller中创建一个新的类LogoutServlet
4.6.3 编写前端代码
这里直接通过使用a标签进行实现,不需要使用ajax来实现
我们将详情页、编辑页、列表页中退出登录均改成a标签
4.7 发布博客
4.7.1 约定前后端交互所用接口
请求:
POST /blog
Content-Type:application/x-www-form=urlencoded
title=标题&content=正文
响应:
HTTP/1.1 302
Location:blog-list.html
4.7.2 编写后端代码
把请求中的博客数据拿到同时写入数据库
在blogServler中添加如下代码:
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf8");
// 1. 获取到用户的登录状态.
HttpSession session = req.getSession(false);
if (session == null) {
resp.setStatus(403);
return;
}
User user = (User) session.getAttribute("user");
if (user == null) {
resp.setStatus(403);
return;
}
// 2. 读取请求的内容
String title = req.getParameter("title");
String content = req.getParameter("content");
if (title == null || title.equals("") || content == null || content.equals("")) {
resp.setStatus(400);
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("请求中的标题或正文不完整");
return;
}
// 3. 构造 Blog 对象, 并插入到数据库中.
Blog blog = new Blog();
blog.setTitle(title);
blog.setContent(content);
// 博客的作者. 作者是谁? 当前谁登录, 作者就是谁!!
blog.setUserId(user.getUserId());
BlogDao blogDao = new BlogDao();
blogDao.insert(blog);
// 4. 插入成功之后, 跳转到博客列表页.
resp.sendRedirect("blog_list.html");
}
在BlogDao中修改一下查询语句:使得我们查询出来的内容是按照发布时间进行排序的,最新发布的位于最前
String sql = "select * from blog order by postTime desc";
4.7.3 编写前端代码
首先我们需要在div中添加form
其次在input type=“text” 添加name属性、将发布文章改为submit
在script 标签中添加:
// 加上这个属性, 效果就是把编辑器里的内容给自动保存到 textarea 里.
saveHTMLToTextArea: true,
4.8 删除博客
4.8.3 编写后端代码
对UserInfo的接口做出修改,从而判断当前登录的用户是否是同一个人,如果是则返回true,如果不是则返回false。然后前端可以通过返回的结果来决定是否删除按钮
在User里添加如下代码:
private int isYourBlog = 0;
public int getIsYourBlog() {
return isYourBlog;
}
public void setIsYourBlog(int isYourBlog) {
this.isYourBlog = isYourBlog;
}
在UserInfoServlet里添加:
if (user.getUserId() == author.getUserId()) {
author.setIsYourBlog(1);
} else {
author.setIsYourBlog(0);
}
然后在controller中新建一个BlogDeleteServlet
4.8.3 编写前端代码
如果当前博客作者是登录用户自己,则在详情页导航栏中显示这个删除按钮
如果当前博客不是登录的用户,则不显示删除按钮
我们通过复用这个接口,根据结果判断是否显示接口
小结
到这里我们的一个简单的博客系统项目算是告一段落了。整个系统中涉及到两个表、四个页面以及上述这些功能。但是项目仍然还有很多需要补充的地方:如用户注册、用户头像、博客中插入图片等众多功能。这些仍需要我们继续去拓展。
在整个系统完成过程中也遇到了很多问题:前端页面的实现、前后端交互是出现的编码错误、在插入博客数据后无法正确返回等等。
在第一次完成项目时,我们会遇到很多困难,而这些也是我们复习知识最好的方式
当我们遇到问题时,一定不要放弃。通过输出日志、添加断点调试等工具慢慢细心的寻找错误。学会调试的过程就是我们成长的过程!
“让我们成为哪种人的不是我们的能力,而是我们的选择。”