准备工作
1.创建项目
2.引入依赖
我的数据库:8.0.32版本,导入的驱动便是32版本
我的tomcat是8.5版本,所以Servlet的依赖便是3.1版本。
另一个json依赖随意,版本别太低就行。(如何导入依赖)
(具体情况可以看下面的一篇文章链接,关于Servlet)
<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/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.32</version>
</dependency>
</dependencies>
3.创建必要的目录
数据库设计
1.博客数据
名字:blog
字段:id,标题(title),正文(content),userID,发布时间(postTime)
2.用户数据
名字:user
字段:userID,username(姓名),password(密码)
对于建表的sql,会单独搞一个sql文件,进行了保存。目的是为了在不同的主机上进行部署,我们就需要将数据库也创建好。
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(128),
content varchar(4096),
userId int,
postTime datetime
);
drop table if exists user;
create table user(
userId int primary key auto_increment,
username varchar(20) unique ,
password varchar(20)
);
insert into user values (null,"zhangsan","123456"),(null,"lisi","123546");
封装数据库操作(java)
就是对JDBC进行封装
1.封装数据库链接
创建一个目录model
在这个目录中建一个java文件(DBUtil),这个类用来封装数据库链接。
?号前面是数据库,后面不用管。
因为考虑到线程安全的问题,所以我这里使用了懒汉模式(具体情况可以去看下面我的文章:多线程)
import com.mysql.cj.jdbc.MysqlDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class DBUtil {
private volatile static DataSource dataSource=null;
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/blog_system?characterEncoding=utf8&useSSL=false");
((MysqlDataSource)dataSource).setUser("root");
((MysqlDataSource)dataSource).setPassword("xietian1314");
}
}
}
return dataSource;
}
public static Connection getConnection() throws SQLException {
//建立连接
return getDataSource().getConnection();
}
public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet){
if (resultSet!=null){
try {
resultSet.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if (statement!=null){
try {
statement.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if (connection!=null){
try {
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}
close方法中的关闭不能调换顺序(详情可以看我下面的文章:mysql)
2.创建实例对象(数据表有两个)
所以实例对象java文件也有两个。
- User:对应数据库中的user表(字段有什么,那么User的成员就对应什么)
- Blog:对应数据库中的blog表(字段有什么,那么Blog的成员就对应什么)
Blog类
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
public class Blog {
private int blogId;
private String title;
private String content;
private int userId;
private Timestamp postTime;
public int getBlogId() {
return this.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 int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
// public Timestamp getPostTime() {
// return postTime;
// }
public String getPostTime(){
//返回格式化时间
SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return simpleDateFormat.format(postTime);
}
public void setPostTime(Timestamp postTime) {
this.postTime = postTime;
}
}
User类
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;
}
}
这里的get和set方法不需要自己敲,具体情况可以看我下面文章中:java中,目录:类与方法中体现
3.封装必要的增删改查操作
因为有两张表,所以需要分别对两张表的增删改查添加方法代码(UserDao,BlogDao)
Dao:data access object:通过这样的对象来访问数据。
BlogDao类
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 {
//把一个Blog对象插入到数据库中
public void insert(Blog blog){
Connection connection=null;
PreparedStatement statement=null;
try {
//建立连接
connection=DBUtil.getConnection();
//构造sql
String sql="insert into blog values(null,?,?,?,?)";
statement=connection.prepareStatement(sql);
statement.setString(1,blog.getTitle());
statement.setString(2,blog.getContent());
statement.setInt(3,blog.getUserId());
statement.setString(4,blog.getPostTime());
//执行sql
statement.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
DBUtil.close(connection,statement,null);
}
}
//查询blog表中所以的博客数据
public List<Blog> selectAll(){
List<Blog> list=new ArrayList<>();
Connection connection=null;
PreparedStatement statement=null;
ResultSet resultSet=null;
try {
connection=DBUtil.getConnection();
String sql="select * from blog order by postTime desc ";
statement=connection.prepareStatement(sql);
resultSet=statement.executeQuery();
while (resultSet.next()){
Blog blog=new Blog();
blog.setBlogId(resultSet.getInt("blogId"));
blog.setTitle(resultSet.getString("titlie"));
//摘取100个字符长度做摘要
if (content.length()>100){
content+=content.substring(0,100)+"...";
}
blog.setContent(content);
blog.setUserId(resultSet.getInt("userId"));
blog.setPostTime(resultSet.getTimestamp("postTime"));
list.add(blog);
}
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
DBUtil.close(connection,statement,resultSet);
}
return list;
}
//查询一个博客id来查询对应的博客
public Blog selectOne(int blogId){
Connection connection=null;
PreparedStatement statement=null;
ResultSet resultSet=null;
try {
connection=DBUtil.getConnection();
String sql="select * from blog where blogId=?";
statement=connection.prepareStatement(sql);
statement.setInt(1,blogId);
resultSet=statement.executeQuery();
if (resultSet.next()){
Blog blog=new Blog();
blog.setBlogId(resultSet.getInt("blogId"));
blog.setTitle(resultSet.getString("title"));
blog.setContent(resultSet.getString("content"));
blog.setUserId(resultSet.getInt("userId"));
blog.setPostTime(resultSet.getTimestamp("postTime"));
return blog;
}
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
DBUtil.close(connection,statement,resultSet);
}
return null;
}
//根据一个博客id来删除对应的博客
public void delete(int blogId){
Connection connection=null;
PreparedStatement statement=null;
try {
connection=DBUtil.getConnection();
String sql="delete from blog where blogId=?";
statement=connection.prepareStatement(sql);
statement.setInt(1,blogId);
statement.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
DBUtil.close(connection,statement,null);
}
}
}
UserDao
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class UserDao {
//新增先不管
public User selectUserById(int userId){
Connection connection=null;
PreparedStatement statement=null;
ResultSet resultSet=null;
try {
connection=DBUtil.getConnection();
String sql="select * from user where userId=?";
statement=connection.prepareStatement(sql);
statement.setInt(1,userId);
resultSet=statement.executeQuery();
if (resultSet.next()){
User uer=new User();
uer.setUserId(resultSet.getInt("userId"));
uer.setUsername(resultSet.getString("username"));
uer.setPassword(resultSet.getString("password"));
return uer;
}
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
DBUtil.close(connection,statement,resultSet);
}
return null;
}
public User selectUserByName(String username){
//无法重复,数据库写了unique
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 uer=new User();
uer.setUserId(resultSet.getInt("userId"));
uer.setUsername(resultSet.getString("username"));
uer.setPassword(resultSet.getString("password"));
return uer;
}
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
DBUtil.close(connection,statement,resultSet);
}
return null;
}
}
前后端逻辑的实现
博客列表页
展示博客列表
1.约定前后端交互接口
请求: Get /blog
响应:json类型
2.写后端代码
创建一个Servlet来处理对应的请求
import com.fasterxml.jackson.databind.ObjectMapper;
import model.Blog;
import model.BlogDao;
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 java.io.IOException;
import java.util.List;
@WebServlet("/blog")
public class BlogServlet extends HttpServlet {
private ObjectMapper objectMapper=new ObjectMapper();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
BlogDao blogDao=new BlogDao();
List<Blog> blogs=blogDao.selectAll();
String respString=objectMapper.writeValueAsString(blogs);
resp.setContentType("application/json;charset=utf8");
resp.getWriter().write(respString);
}
}
3.写前端代码
在前端构造ajax请求,按照上述约定,发送http请求
具体前端页面可以看我下面的文章(博客设计(项目前端))这里将ajax主要代码写一下。
用ajax实现构造div,实现获取到所有的博客数据,并且构造到页面上(blog_list.html)
<script>
// 通过ajax给服务器发请求,获取到所有的博客数据,并且构造到页面上
function getBlogs(){
$.ajax({
type:'get',
url:'blog',
success:function(body){
let containerRight=document.querySelector('.container-right');
for(let blog of body){
let blogDiv=document.createElement("div");
blogDiv.className='blog';
let titleDiv=document.createElement("div");
titleDiv.className='title';
titleDiv.innerHTML=blog.title;
let dateDiv=decument.createElement("div");
dateDiv.className='date';
dateDiv.innerHTML=blog.postTime;
let descDiv=decument.createElement("div");
descDiv.className='desc';
descDiv.createElement=blog.content;
let a=document.createElement("a");
a.href='blog_datail.html?Id='+blog.blogID;
a.innerHTML='查看全文>>';
//将构造好的标签构造好之后,还需要组合起来
blogDiv.appendChild(titleDiv);
blogDiv.appendChild(dateDiv);
blogDiv.appendChild(descDiv);
blogDiv.appendChild(a);
containerRight.appendChild(blogDiv);
}
}
});
}
getBlogs();
</script>
此时我们往数据库里插入一些数据,运行服务器和页面后,发现时间,为时间戳而不是格式化时间,博客的顺序也有问题,应该是最新的在上面。所以我们就需要查明问题的原因在哪里。通过抓包工具我发现抓到的数据是时间戳,意味着服务器返回的时间是时间戳。
(后端返回的数据是Jackson把blogs转化成json字符串,直接返回 Jason会遍历当前的blogs List.不要的对象去调用不了的对象的那个get方法,获取到对应的属性,构造成json字符串。)
所以我将关于时间的get方法给重写了,通过SimpleDateFormat格式化(但是格式不统一,用之前一定要查)
顺序:通过对时间进行排序完成(代码已做修改)
博客详情页
1.约定前后端交互接口
请求: Get /blog/blogId=?
响应:json类型
2.编写后端代码
从query string 中查询一下是否有blogId,如果有就认为是指定博客;如果没有就是查询所有博客
@WebServlet("/blog")
public class BlogServlet extends HttpServlet {
private ObjectMapper objectMapper=new ObjectMapper();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//从query string 中查询一下是否有blogId,如果有就认为是指定博客;如果没有就是查询所有博客
BlogDao blogDao=new BlogDao();
String blogId=req.getParameter("blogId");
if (blogId==null){
List<Blog> blogs=blogDao.selectAll();
String respString=objectMapper.writeValueAsString(blogs);
resp.setContentType("application/json;charset=utf8");
resp.getWriter().write(respString);
}else {
Blog blog = blogDao.selectOne(Integer.parseInt(blogId));
String respString = objectMapper.writeValueAsString(blog);
resp.setContentType("application/json;charset=utf8");
resp.getWriter().write(respString);
}
}
}
3.编写前端代码
跟上面一样,只粘贴最重要的部分。(具体情况看我的前端)
<script>
function getBlog(){
$.ajax({
type:'get',
url:'blog'+location.search,
success:function(body){
//设置博客标题
let h3=document.querySelector('.conrainer-right h3');
h3.innerHTML=body.title;
//设置发布时间
let dateDiv=document.querySelector('.conrainer-right .date')
dateDiv.innerHTML=body.postTime;
//设置正文,正文内容应该是markdown格式的数据
//此处应该是渲染后的markdown内容。而不是markdown的原始字符串
//第一个参数,是一个html元素的id,接下来来渲染结果放到对应的元素中
editormd.markdownToHTML('content',{markdown:body.content});
}
});
}
getBlog();
</script>
问题:
此时运行代码,发现代码根本没有渲染,原因在于浏览器缓存,从网络上获取数据的速度较为慢,所以浏览器呢对于这种情况,将一些数据放在了浏览器缓存中,等下一次或点击获取的时候会直接显示这个内容。从而就避免了。因网络原因所造成的明显风险。但是在我们这个页面中发现它并没有发生改变,其原因就在于这个问题我们需要将缓存进行更新,让他强制刷新一下。F5+ctrl;
此时可能会出现一些问题
比如导入的依赖出现了问题,换一个就好了。
登录功能
1.约定前后端交互接口
请求:
post /login
content-type:application/x-www-form=urlencided
query string 格式;
响应:
http/1.1 302
Location:blog_list.html
如果通过302跳转
前端必须使用from,不能用ajax,ajax,收到响应,不会触发页面跳转
2.编写后端代码
创建一个class类LoginServlet
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;
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置编码
req.setCharacterEncoding("utf8");
String username=req.getParameter("username");
String password=req.getParameter("password");
if (username==null||username.equals("")||password==null||password.equals("")){
String html="<h3> 登录失败!缺少用户名密码 </h3>";
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write(html);
return;
}
UserDao userDao=new UserDao();
User user=userDao.selectUserByName(username);
if (user==null){
//用户名不存在
String html="<h3>用户名或密码</h3>";
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write(html);
return;
}
if (!password.equals(user.getPassword())){
//用户名不存在
String html="<h3>用户名或密码</h3>";
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write(html);
return;
}
//登录成功建立会话。理解成一个哈希表
HttpSession session= req.getSession(true);
session.setAttribute("user",user);
//返回一个重定向响应
resp.sendRedirect("blog_list");
}
}
3.编写前端代码
这个没啥好说的。主要可以去看我的HTML板块
<div class="login-container">
<div class="login-dialog">
<h3>登录</h3>
<!-- 用form包裹,方便后续代码的在提交给服务器 -->
<form action="login" method="post">
<div class="row">
<span>用户名</span>
<input type="text" id="username" name="username">
</div>
<div class="row">
<span>密码</span>
<input type="password" id="password" name="password">
</div>
<div class="row">
<input type="submit" id="submit" value="登录">
</div>
</form>
</div>
</div>
强制登录设定
类似于在网上淘宝购物时,无论点什么都要登录。
在博客列表页,详情页登录页。页面加载的时候发起一个。ajax请求,通过这个请求访问服务器,获取当前的登录状态,如果当前灯未登录则跳转登录页面,如果已登录则不做任何处理。
1.约定一个前后端交互接口
请求:
get /login
响应:
登录成功,返回200这样的响应,body可以不要。登陆失败(未登录),返回403这样的响应。
2.后端代码
在LoginServlet写一个doget判断当前状态。
固定操作。
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
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;
}
resp.setStatus(200);
}
3.前端代码
(具体代码看我项目前端)
function getLoginStatus(){
$.ajax({
type:'get',
url:'login',
success:function(body){
//响应200的时候,执行success回调
//用户已经登录。啥都不用干
console.log("用户已登录");
},
error:function(body){
//响应403
Location.assign("login.html");
}
});
}
显示作者信息
1.在博客列表页,要获取到登录用的信息
1.前后端交互
通过Json将当前信息返回回来
请求:GET/login
响应:json格式
2.后端代码
resp.setStatus(200);
resp.setContentType("application/json;charset=utf8");
user.setPassword("");
String respJson=objectMapper.writeValueAsString(user);
resp.getWriter().write(respJson);
3.前端代码
2.在博客详情页,要获取文章作者的信息
1.前后端交互
请求:Get /user?blogId=1
响应:json格式
2.后端代码
建立一个新的java类
@WebServlet("/user")
public class UserServlet extends HttpServlet {
private ObjectMapper objectMapper=new ObjectMapper();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("application/json;charset=utf8");
String blogId=req.getParameter("blogId");
if (blogId==null||blogId.equals("blogId")){
String respJson=objectMapper.writeValueAsString(new User());
resp.getWriter().write(respJson);
System.out.println("参数blogId为空");
return;
}
BlogDao blogDao=new BlogDao();
Blog blog=blogDao.selectOne(Integer.parseInt(blogId));
if (blog==null){
String respJson=objectMapper.writeValueAsString(new User());
resp.getWriter().write(respJson);
System.out.println("参数blogId不存在");
return;
}
UserDao userDao=new UserDao();
User user=userDao.selectUserById(blog.getUserId());
if (user==null){
String respJson=objectMapper.writeValueAsString(new User());
resp.getWriter().write(respJson);
System.out.println("作者博客不存在");
return;
}
String respJson=objectMapper.writeValueAsString(user);
resp.getWriter().write(respJson);
}
}
3.前端代码
function getAuther(){
$.ajax({
type:'get',
url:'user'+location.search,
success:function(body){
if(body.userId==0){
alert("当前为找到作者信息");
return;
}
let h3=document.querySelector('.card h3');
h3.innerHTML=body.username;
}
});
}
getAuther();
注销
1.前后端交互
请求:
GET /logout
响应:
HTTP/1.1
location:login.html
2.编写后端
根据logout路径,设定新的Servlet
@WebServlet("logout")
public class LogoutServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session=req.getSession(false);
if (session==null){
resp.sendRedirect("login.html");
return;
}
//user存在,就删除了,不存在,也不会有副作用
session.removeAttribute("user");
resp.sendRedirect("login.html");
}
}
3.编写前端
实现发布博客
用户写的博客标题和正文,就可以随着点击发布,从而进行上传,服务器可以保存到数据库。
1.约定前后端交互接口
请求:POST /blog
响应:302 Location:blog_list.html
2.实现后端代码
从请求中拿到标题和正文
从会话中拿到用户的登录状态(作者id)
给blog对象
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String title=req.getParameter("title");
String content= req.getParameter("content");
if (title==null||title.equals("")||content==null||content.equals("")){
String html="<h3> title 或者 content 为空!新增博客失败!</h3>";
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write(html);
return;
}
//2.从会话中拿到作者的id
HttpSession session= req.getSession(false);
if (session==null){
String html="<h3> 用户未登录!新增博客失败!</h3>";
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write(html);
return;
}
User user=(User) session.getAttribute("user");
if (user == null) {
String html="<h3> 用户未登录!新增博客失败!</h3>";
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write(html);
return;
}
//构造blog对象
Blog blog=new Blog();
blog.setUserId(user.getUserId());
blog.setTitle(title);
blog.setContent(content);
blog.setPostTime(new Timestamp(System.currentTimeMillis()));
BlogDao blogDao=new BlogDao();
blogDao.insert(blog);
resp.sendRedirect("blog_list.html");
}
3.实现前端代码
如何把编辑框内的 MD内容作为form的一部分交给服务器?在editor点MD中的官方文件档中,demo里给。里面提示先给#editor div中放一个隐藏的textarea,后续编辑器输入框的内容就会被自动放到这个textarea中。在初始化代码中需要新增一个选项,savehtmltotextarea。
<div class="blog-edit-container">
<form action="blog" method="post">
<div class="title">
<input type="text" id="title-input" name="title">
<input type="submit" id="submit">
</div>
<div id="editor">
<textarea name="content" style="display: none;"></textarea>
</div>
</form>
</div>
<script>
var editor=editormd("editor",{
width:"100%",
height:"calc(100% - 50px)",
markdown:"# 在这里写下一篇博客",
path:"editor.md/lib/",
saveHTMLTotextarea:true
});
getLoginStatus();
</script>
总结
构造http请求有很多方式
1.地址栏,输入url
2.特殊的html标签,a,img,link,script
3.form (form 会跳转页面,只支持get和post)
4.ajax (ajax不会跳转页面(可以使用js跳转) Location.assign(“login.html”);)
5.其他语言编写的客户端