目录

一.准备工作

二.实现数据库代码(JDBC)

1.创建数据库/表结构==>数据库设计

2.封装数据库(Model)

1>创建DBtil封装数据库连接操作

2>创建实体类-->表示一条记录

3>封装针对数据的增删改查

三.博客列表页

1.约定前后端

2.编写服务器代码

3.编写客户端代码

四.博客详情页

1.约定前后端交互接口

2.编写服务器代码

3.编写客户端代码

五.博客登录页

1.约定前后端交互接口

2.编写客户端代码

3.编写服务器代码

六.登录功能完成后,要调整一下,之前的两个页面

1.约定前后端交互接口

2.编写服务器代码

3.编写客户端代码

七.显示用户信息

1.博客列表页,显示用户信息,是当前登录的用户

2.在博客详情页,显示用户的信息是当前文章的作者信息

1.编写服务器代码

3.编写服务器代码

八:实现"注销功能"

1.约定前后端接口

2.编写服务器代码

步骤

3.编写客户端代码

九.发布博客

1.约定前后端交互接口

2.编写服务器代码

步骤

3.编写客户端代码

十.删除博客

1.约定前后端接口

2.编写客户端代码

步骤

3.编写服务器代码

步骤

十一.项目部署

1.前期工作

1.1tomcat

1.2.Mysql

2.代码微调

3.打包

4.进行部署

1.把war拷贝到云服务器上的webapps目录

2.启动tomcat服务器

3.验证tomcat是否启动成功


前言

       这个博客系统采用的是目前现在更主流的开发方式是 " 前后端分离 " 的方式 . 这种方式下服务器端不关注页面的内容 , 而只是给网页端提供数据. 网页端通过 ajax 的方式和服务器之间交互数据 , 网页拿到数据之后再根据数据的内容渲染到页面上 .
设计思路:
  • 实现数据库代码,并进行封装;首先创建实现了数据库的初始化,然后建立了两个实体类,然后分别对User表进行了查找功能和Blog表的插入、查找和删除功能.
  • 通过Servlet设计并实现具体功能,包括登录,浏览,发表,删除.

一.准备工作

1.创建maven项目~

2.引入依赖. servlet, jackson, mysql

3.创建必要的目录~

4.编写代码~

5/6.打包部署(直接基于smart tomcat)

7.在浏览器中验证

正式编写

把之前的前端页面引入到项目中,直接拷贝到webapp目录下

这些文件可以理解成静态资源

二.实现数据库代码(JDBC)

1.创建数据库/表结构==>数据库设计

前面写博客页面的时候,搞过这么几个页面.

1.博客列表页.显示博客的列表.

2.博客详情页.点击博客列表页, .上面列出的博客条目,跳转到的页面,显示博客的完整内容.

3.登录页

4.博客编辑页.基于editor.md搞了-个markdown编辑器~~基于这个页面来发布博客了.

==>

存储博客~当点击发布的时候,博客被发布到服务器上,就要被存起来获取博客~

在博客列表页和博客详情页,能够拿到博客的内容.

还能够进行登录校验~~

==>

设计表,就需要抓住需求中的实体.(关键性的名词)

博客表用来存储所有的博客数据~~

用户表,用户登录,就需要用到这个表~

创建dp.sql

创建java1_blog库

库中创建blog表(blogId , title, content , userId, postTime)和user表(userId ,username, password)

-- 编写建库建表的sqlcreate database if not exists java1_blog;use java1_blog;-- 创建一个博客表
drop table if exists blog;
create table blog(blogId int primary key auto_increment,title varchar(1024),content mediumtext,userId int,-- 文章作者的idpostTime datetime -- 发布时间
);-- 创建用户表
drop table if exists user;
create table user(userId int primary key auto_increment,username varchar(128) unique,password varchar(128)
);insert into user values(null,'zhang','123');
insert into user values(null,'lisi','123');

把dp.sql中的代码,赋值到mysql中,并检查是否创建成功

2.封装数据库(Model)

1>创建DBtil封装数据库连接操作

1.创建数据源(懒汉模式,要解决线程安全问题)

2.建立连接

3.关闭

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;/*** @author KYF* @create 2023-04-21*/
//使用这个类和数据库建立连接
public class DBUtil {private static final String URL="jdbc:mysql://127.0.0.1:3306/java1_blog?characterEncoding=utf8&useSSL=false";private static final String USERNAME="root";private static final String PASSWORD="123456";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(URL);((MysqlDataSource)dataSource).setUser(USERNAME);((MysqlDataSource)dataSource).setPassword(PASSWORD);}}}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) {e.printStackTrace();}}if(statement!=null){try {statement.close();} catch (SQLException e) {e.printStackTrace();}}if(connection!=null){try {connection.close();} catch (SQLException e) {e.printStackTrace();}}}}

2>创建实体类-->表示一条记录

创建Blog和User类

//每个blog对象表示blog表中的一条记录
public class Blog {private int blogId;private String title;//标题private String content;//正文private int userId;//用户idprivate Timestamp postTime;//发布时间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 int getUserId() {return userId;}public void setUserId(int userId) {this.userId = userId;}//    public Timestamp getPostTime() {
//        return postTime;
//    }//不返回时间戳对象,返回一个String(格式化好的时间)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对象表示User表中的一条记录
public class User {private int userId;private String username;private String password;public int getUserId(int userId) {return this.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;}
}

3>封装针对数据的增删改查

1.和数据库建立连接

2.构造SQL语句

3.执行SQL

4.关闭连接,释放资源

封装博客表的基本操作:增删查

package model;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;/*** @author KYF* @create 2023-04-21*/
//封装博客表的基本操作
public class BlogDao {//1.往博客表中,插入博客public void insert(Blog blog){//JDBC基本代码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.执行SQLstatement.executeUpdate();} catch (SQLException e) {e.printStackTrace();}finally {//4.关闭连接,释放资源DBUtil.close(connection,statement,null);}}//2.获取博客表中的所有博客信息(用于博客列表页)public List<Blog> selectAll(){Connection connection=null;PreparedStatement statement=null;ResultSet resultSet=null;List<Blog> blogs=new ArrayList<>();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("title"));//要截断(太长要去掉后面)String content=resultSet.getString("content");if(content.length()>50){content=content.substring(0,50)+"...";}blog.setContent(content);blog.setUserId(resultSet.getShort("userId"));blog.setPostTime(resultSet.getTimestamp("postTime"));blogs.add(blog);}} catch (SQLException e) {e.printStackTrace();}finally {DBUtil.close(connection,statement,resultSet);}return blogs;}//3.根据博客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();//因为是主键,查询结果要么是0,要么是1if(resultSet.next()){Blog blog=new Blog();blog.setBlogId(resultSet.getInt("blogId"));blog.setTitle(resultSet.getString("title"));blog.setContent(resultSet.getString("content"));blog.setUserId(resultSet.getShort("userId"));blog.setPostTime(resultSet.getTimestamp("postTime"));return blog;}} catch (SQLException e) {e.printStackTrace();}finally {DBUtil.close(connection,statement,resultSet);}return null;}//4.从博客表,根据博客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) {e.printStackTrace();}finally {DBUtil.close(connection,statement,null);}}//没有改
}

用户表的基本操作:

1.根据用户名查找用户信息

登录中使用

2.根据用户Id用户信息

博客详情页,根据用户ID查询名字,把作者名字显示出来

package model;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;/*** @author KYF* @create 2023-04-21*/
//提供了针对用户表的基本操作
public class UserDao {//1.根据用户名查找用户信息//登录中使用public User selectByName(String username){Connection connection=null;PreparedStatement statement=null;ResultSet resultSet=null;try{connection=DBUtil.getConnection();String sql="select * from user blog 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 e) {e.printStackTrace();}finally {DBUtil.close(connection,statement,resultSet);}return null;}//2.根据用户Id用户信息//博客详情页,根据用户ID查询名字,把作者名字显示出来public User selectById(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 user=new User();user.setUserId(resultSet.getInt("userId"));user.setUsername(resultSet.getString("username"));user.setPassword(resultSet.getString("password"));return user;}} catch (SQLException e) {e.printStackTrace();}finally {DBUtil.close(connection,statement,resultSet);}return null;}
}

三.博客列表页

能展示数据库中的列表,页面加载,要通过ajax发起HTTP请求,从服务器获取到博客列表页数据

1.约定前后端

请求:

GET/blog

响应:(json数组)

[

{

blogId:1,

title:'这是第一篇博客',

content:'这是博客正文',(正文的摘要),要截取正文

userId:1,

postTime:'2023-04-20 15:00:00'

},

{

blogId:2,

title:'这是第一篇博客',

content:'这是博客正文',

userId:1,

postTime:'2023-04-20 15:00:00'

},

........

]

2.编写服务器代码

获取数据库中的博客列表:

  1. 从数据库中查询博客列表,转成json给格式返回
  2. 把blogs转成JSON格式
package controller;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;/*** @author KYF* @create 2023-04-21*/
//处理/blog路径对应的请求
@WebServlet("/blog")
public class BlogServlet extends HttpServlet {//获取数据库中的博客列表private ObjectMapper objectMapper=new ObjectMapper();@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//从数据库中查询博客列表,转成json给格式返回BlogDao blogDao=new BlogDao();List<Blog> blogs=blogDao.selectAll();//把blogs转成JSON格式String respJson=objectMapper.writeValueAsString(blogs);//顺序不能颠倒-->先写body,设置的ContentType就不会生效resp.setContentType("application/json;charset=utf8");resp.getWriter().write(respJson);}
}

3.编写客户端代码

在页面加载时,让页面通过ajax访问服务器,获取到数据库中的博客数据,并填到页面中

获取到的body是一个js对象数组,每个元素就是一个js对象

1.先把.right原有的内容清空

2.遍历body,构造出一个个blogDiv

3.在每一个blogDiv中构造所有信息

  1. 创建一个结点
  2. 命名
  3. 得到blog中的信息
  4. 插入到blogDiv中
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>博客列表</title><!-- 外部的css要通过link引入 --><link rel="stylesheet" href="CSS/commin.css"><link rel="stylesheet" href="CSS/blog_list.css">
</head>
<body><!-- 这个是导航栏 --><div class="nav"><img src="data:image/flower.jpg" alt=""><span>我的博客系统</span><!-- 空白元素,用来占位置 --><div class="spacer"></div><a href="blog_list.html">主页</a><a href="blog_edit.html">写博客</a><a href="#">注销</a></div><!-- container作为版心 --><div class="container"><!-- 左侧个人信息 --><div class="left"><!-- 表示整个用户信息区域 --><div class="card"><img src="data:image/girl.jpg" alt=""><h3>沐晴</h3><a href="#">github 地址</a><div class="counter"><span>文章</span><span>分类</span> </div><div class="counter"><span>2</span><span>1</span></div></div></div><!-- 右侧内容详情 --><div class="right"><!-- 一个blog对应一个博客 --><div class="blog"><!-- 博客标题 --><div class="title">我的第一篇博客</div><!-- 博客发布时间 --><div class="date">2023-04-04 22:39:00</div><!-- 博客摘要 --><div class="desc">风羽,落花依依。独自行走在田野间的小路,氤氲在心上的离愁,始终带着些许迷离,些许感伤。记忆中,往昔的片段,总有太多不舍。故事的结局里,总是萦绕着心碎的痛楚。   </div><a href="blog_detail.html">查看全文&gt; &gt;</a></div><!-- 一个blog对应一个博客 --><div class="blog"><!-- 博客标题 --><div class="title">我的第二篇博客</div><!-- 博客发布时间 --><div class="date">2023-04-05 20:39:00</div><!-- 博客摘要 --><div class="desc">给我一滴水,我为你化成大海,让你游进幸福的港湾,给我一点阳光,我为你化成美丽的春天,让你飞到无忧的天地。亲爱的,周末愉快。</div><a href="#">查看全文&gt; &gt;</a></div></div></div><script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script><script>//在页面加载时,通过ajax给服务器发送数据,并获取到博客列表信息,并显示在页面上function getBlogList(){$.ajax({type:'get',url:'blog',success:function(body){//获取到的body是一个js对象数组,每个元素就是一个js对象//1.先把.right原有的内容清空let rightDiv=document.querySelector('.right');rightDiv.innerHTML='';//2.遍历body,构造出一个个blogDivfor(let blog of body){let blogDiv=document.createElement('div');blogDiv.className='blog';//构造标题let titleDiv=document.createElement('div');titleDiv.className='title';titleDiv.innerHTML=blog.title;blogDiv.appendChild(titleDiv);//构造发布时间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='查看全文 &gt;&gt';//此处点击跳转博客详情页//这个跳转过程要告知服务器要访问哪个博客详情页//获取博客详情页发送的请求a.href='blog_detail.html?blogId='+blog.blogId;blogDiv.appendChild(a);//把blogDiv挂到dom树上rightDiv.appendChild(blogDiv);}},error:function(){alert("获取博客列表失败!");}});}getBlogList();</script>
</body>
</html>

页面加载时,触发ajax请求来访问服务器,获取到博客内容再次填充到博客详情页

四.博客详情页

在博客列表页点击查看详情,跳转到详情页,看到详情页正文

1.约定前后端交互接口

请求:

GET/blog?blogId=1(请求有参数)

响应

HTTP/1.1 200 OK

Content-Type:application/json

(响应结果不是json数组,只是单一对象)

{

blogId:1,

title:'这是第一篇博客',

content:'这是博客正文',(不发生截断)

userId:1,

postTime:'2023-04-20 15:00:00'

}

2.编写服务器代码

后端代码实现和博客列表页的获取基本相同,就放在了一个方法中来实现,使用blogId参数来区分是获取博客列表还是详情

  1. 根据参数判断返回博客列表,还是详情页(有参数)
  2. 先尝试获取req的blogId参数,如果参数存在,说明是博客详情页
  3. 如果不存在,说明要请求博客的列表
    1. 得到blogId
    2. 通过blogId得到对应的博客
    3. 把Blog类型转换成Json类型
 package controller;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;/*** @author KYF* @create 2023-04-21*/
//处理/blog路径对应的请求
@WebServlet("/blog")
public class BlogServlet extends HttpServlet {//获取数据库中的博客列表private ObjectMapper objectMapper=new ObjectMapper();@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {BlogDao blogDao=new BlogDao();resp.setContentType("application/json;charset=utf8");//根据参数判断返回博客列表,还是详情页(有参数)//先尝试获取req的blogId参数,如果参数存在,说明是博客详情页//如果不存在,说明要请求博客的列表String param=req.getParameter("blogId");if(param==null){//没有参数,获取博客列表页//从数据库中查询博客列表,转成json给格式返回List<Blog> blogs=blogDao.selectAll();//把blogs转成JSON格式String respJson=objectMapper.writeValueAsString(blogs);//顺序不能颠倒-->先写body,设置的ContentType就不会生效resp.getWriter().write(respJson);}else{//有参数,获取博客详情页int blogId=Integer.parseInt(param);Blog blog=blogDao.selectOne(blogId);String respJson=objectMapper.writeValueAsString(blog);resp.getWriter().write(respJson);}}
}

3.编写客户端代码

修改blog_detail.html,在页面加载时,能调用上述接口,来从服务器获取博客数据

1.构造请求/blog?blogId=5

在博客详情页拿到博客的具体内容,要构造一个请求/blog?blogId=5(这个参数告诉服务器,要哪个博客)

这个请求是blog_detail.html通过ajax发送,就要构造blogId=5这个参数,5在来到这个页面,url就带上这个参数了,

通过location.search能拿到?blogId=5这段内容,构造/blog?blogId=5这个请求

内容来自博客列表页

这个按钮是一个a标签,href属性

a中带的href属性,有参数了

内容来自于博客列表页代码

let a=document.createElement('a');
a.innerHTML='查看全文 &gt;&gt';
a.href='blog_detail.html?blogId='+blog.blogId;
blogDiv.appendChild(a);

在博客列表页从服务器获取博客列表时,拿到的数据中有构造"查看全文"a标签按钮,构造按钮时,就给href设置的内容中加上了当前博客的blogId

2.博客正文用的是有格式化的markdown数据

如果代码直接把content设为innerHTML,意味着界面最终效果是

而期望的是

处理:

要让markdown文本内容被渲染成有特定样式的html片段,要使用editor.md库来完成

这个库不仅提供了markdown编辑器也提供了渲染功能

加markdown渲染得到的效果

步骤

1.引入editor.md依赖

2.去掉原先的正文用div标签 使用id选择器

3.设置函数getBlogDetail()

通过ajax提交,得到标题,时间和正文

<!DOCTY3.PE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>博客详情页</title><link rel="stylesheet" href="CSS/commin.css"><link rel="stylesheet" href="CSS/blog_detail.css"><!-- 引入 editor.md 的依赖 --><link rel="stylesheet" href="editor.md/css/editormd.min.css" /><script src="js/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>
</head>
<body><!-- 这个是导航栏 --><div class="nav"><img src="data:image/flower.jpg" alt=""><span>我的博客系统</span><!-- 空白元素,用来占位置 --><div class="spacer"></div><a href="blog_list.html">主页</a><a href="blog_edit.html">写博客</a><a href="#">注销</a></div><!-- container作为版心 --><div class="container"><!-- 左侧个人信息 --><div class="left"><!-- 表示整个用户信息区域 --><div class="card"><img src="data:image/girl.jpg" alt=""><h3>沐晴</h3><a href="#">github 地址</a><div class="counter"><span>文章</span><span>分类</span> </div><div class="counter"><span>2</span><span>1</span></div></div></div><!-- 右侧内容详情 --><div class="right"><!-- 使用这个div,来包裹整个博客的详情内容 --><div class="blog-content"><h3>我的第一篇博客</h3><div class="date">2023-04-04 22:39:00</div><!-- 正文 --><div id="content" style="opacity: 60%"></div></div></div></div><script>function getBlogDetail(){$.ajax({type:'get',//location.search拿到了形如'?blogiD=5'这样一段内容url:'blog'+location.search,success:function(body){//根据body中的内容来构造页面//构造标题let h3=document.querySelector(".blog-content>h3");h3.innerHTML=body.title;//构造博客发布时间let dateDiv=document.querySelector('.date');dateDiv.innerHTML=body.postTime;//构造博客正文//如果代码直接把content设为innerHTML,意味着界面最终效果是原始的markdown字符串//需要的是渲染后的带有格式的效果// let content=document.querySelector('#content');// content.innerHTML=body.content;//第一个参数对应id=content的html标签,渲染后得到的html片段会被放到这个标签下editormd.markdownToHTML('content',{markdown:body.content});}});}getBlogDetail();</script></body>
</html>

五.博客登录页

用户访问login.html,输入用户名和密码,点击登录按钮,发起一个请求,把用户名和密码交给服务器;服务器对身份进行验证,若验证成功,页面跳转到博客列表页,若失败,就返回错误

1.约定前后端交互接口

1.请求:点击按钮发送请求

POST/login

Content-type:application/x-www-form-urlencode

(如果要form表单,就要把按钮改成input type="submit")

usernmae=zhangsan&password=123

2.响应:验证身份,跳转到博客列表页

HTTP/1.1 302

Location:blog_list.html

(登录成功后,跳转到主页(博客列表页))

2.编写客户端代码

步骤:

1.给代码套上一层form标签(路径,方法) class标签改成div标签

2.input加上name属性,加上属性,就是后续提交数据的键值对key

name中的值是key,用户输入的值是value(例如:username=zhangsan&password=123)

3.把button按钮换成input标签

问题

前端页面开发,html页面结构很重要,一旦页面结构调整,可能导致css或者js失效

调整后

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>登录页面</title><link rel="stylesheet" href="CSS/commin.css"><link rel="stylesheet" href="CSS/blog_login.css">
</head>
<body><!-- 这个是导航栏 --><div class="nav"><img src="data:image/flower.jpg" alt=""><span>我的博客系统</span><!-- 空白元素,用来占位置 --><div class="spacer"></div><a href="blog_list.html">主页</a><a href="blog_edit.html">写博客</a><!-- 注销没有必要在登录页面展示 --><!-- <a href="#">注销</a> --></div><div class="login-container"><form action="login" method="post"><div class="login-dialog"><h3>登录</h3><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"><!-- <button>提交</button> --><input type="submit"id="submit" value="提交"></div></div></form></div>
</body>
</html>

3.编写服务器代码

1.body的参数要和写法对应

usernmae=zhangsan&password=123

String username=req.getParameter("username");

2.文字格式

前端已经告诉浏览器,格式是utf8

接下来浏览器在输入框中输入汉字,也会使用utf8编码,

但是Servlet默认不是utf8解析,就要告诉serlvet要按照utf8解析请求

req.setCharacterEncoding("utf8");

针对请求进行设置:使用utf8格式进行解析请求

resp.setCharacterEncoding("utf8");

针对响应进行设置:构造的数据是按照utf8构造

3.

List I=null; -->为空(连盒子都没有)

List I=new ArrayList<>(); -->非空,对应一个空的ArrayList(一个空的盒子)

4.步骤:

1.获取请求中的参数-->请求内容缺失,登录失败

2.和数据库中的内容进行比较-->用户名没查得到,或者和密码不匹配,登陆失败

3.如果比较通过,就会创建会话

4.返回一个重定向报文,跳转到博客主页

package controller;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;/*** @author KYF* @create 2023-04-22*/
@WebServlet("/login")
public class LoginServlet extends HttpServlet {@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {req.setCharacterEncoding("utf8");//1.获取请求中的参数String username=req.getParameter("username");String password=req.getParameter("password");if(username==null || "".equals(username) ||password==null || "".equals(password)){//请求内容缺失,登录失败resp.setContentType("text/html");resp.getWriter().write("当前用户名或密码为空");return;}//2.和数据库中的内容进行比较UserDao userDao=new UserDao();User user=userDao.selectByName(username);if(user==null || !user.getPassword().equals(password)){//用户名没查得到,或者和密码不匹配//登陆失败resp.setContentType("text/html;charset=utf8");resp.getWriter().write("用户名或密码错误!");return;}//3.如果比较通过,就会创建会话HttpSession session=req.getSession(true);//把用户信息存储的会话中session.setAttribute("user",user);//4.返回一个重定向报文,跳转到博客主页resp.sendRedirect("blog_list.html");}
}

六.登录功能完成后,要调整一下,之前的两个页面

让这两个页面登录后才能访问

进入博客列表页/详情页,要先检查用户的登录状态

如果当前用户已经是登录状态,才能使用,如果是未登录状态,则强制跳转到login页面

处理:

在博客列表页/详情页,通过ajax访问一下服务器,获取当前的登录状态

如果获取到说明,已经登录,此时可以留在这个页面,如果没有登录,要跳转到登录页面

1.约定前后端交互接口

请求:

GET/login

响应

HTTP/1.1 200 OK

Content-Type:application/json

{

userId:1,

username='zhangsan',

}

(登录了返回当前登录的用户信息;未登录,则直接返回一个userId为0的对象)

2.编写服务器代码

步骤

  1. 检测会话是否存在,不存在说明未登录
  2. 有会话,但是会话中没有user对象,也是未登录
  3. 已经登录的状态,返回

注:不要把密码返回给前端

//用来让前端检测登录状态
private  ObjectMapper objectMapper=new ObjectMapper();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.setContentType("application/json;charset=utf8");HttpSession session=req.getSession(false);if(session==null){//检测会话是否存在,不存在说明未登录User user=new User();resp.getWriter().write(objectMapper.writeValueAsString(user));return;}User user=(User) session.getAttribute("user");if(user==null){//虽然有会话,但是会话中没有user对象,也是未登录user=new User();resp.getWriter().write(objectMapper.writeValueAsString(user));return;}//已经登录的状态//不要把密码返回给前端user.setPassword("");resp.getWriter().write(objectMapper.writeValueAsString(user));
}

3.编写客户端代码

js/common.js

//放页面公共的代码
//加上一个逻辑,通过GET/login这个接口获取登录状态
function getUserInfo(pageName){$.ajax({tupe:'get',url:'login',success:function(body){if(body.userId && body.userId>0){console.log("当前用户登录成功!用户名为:"+body.username);}else{//登录失败//前端页面,跳转到login.htmlalert("当前你未登录!请登录后访问博客列表!");location.assign('blog_login.html');}},error:function(){alert("当前您未登录!请登录后访问博客列表!");location.assign('blog_login.html');}});
}

blog_list.html

 <!-- 引入s文件,执行登录状态的检测 --><script src="js/common.js"></script>
<script>getUserInfo('blog_list.html');
</script>

blog_detail.html

</script><script src="js/common.js"></script><script>getUserInfo(blog_detail.html);</script>

七.显示用户信息

1.博客列表页,显示用户信息,是当前登录的用户

//放页面公共的代码//加上一个逻辑,通过GET/login这个接口获取登录状态
function getUserInfo(pageName){$.ajax({type:'get',url:'login',success:function(body){//判定此处的body是不是一个有效的user对象(userId是否非0)if(body.userId &&body.userId>0){//登录成功!console.log("当前用户登录成功!用户名:"+body.username);//根据当前用户登录情况,把当前用户名设置到界面上if(pageName=='blog_list.html'){changeUserName(body.username);}}else{//登录失败//让前端页面,跳转到login.htmlalert("当前你未登录!请登录后在访问博客列表!");location.assign('blog_login.html');}}, error:function(){alert("当前您未登录!请登录后在访问博客列表!");location.assign('blog_login.html');}});
}  function changeUserName(username){let h3=document.querySelector('.card>h3');h3.innerHTML=username;
}

2.在博客详情页,显示用户的信息是当前文章的作者信息

页面加载,获取用户信息,判定是否登录

博客详情页通过其他的API修改

1.约定接口

让服务器提供一个新的接口,让这个接口让客户端指定blogId,获取到指定blogId的作者信息

1.编写服务器代码

通过这个方法,获取到指定博客的作者信息

1.判断参数是否足够

2.在数据库中进行查找,找到对应的blog对象,根据blog,找到作者信息

blog==null 博客不存在

3.根据blog,查询到用户对象

author==null 用户不存在

4.返回信息(不返回密码)

package controller;import com.fasterxml.jackson.databind.ObjectMapper;
import model.Blog;
import model.BlogDao;
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 java.io.IOException;/*** @author KYF* @create 2023-04-22*/
@WebServlet("/authorInfo")
public class AuthorServlet extends HttpServlet {private ObjectMapper objectMapper=new ObjectMapper();@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.setContentType("application/json;charset=utf8");//通过这个方法,获取到指定博客的作者信息String param=req.getParameter("blogId");if(param==null ||"".equals(param)){//参数少了resp.getWriter().write("{\"ok\":false,\"reason\":\"参数缺失!\"}");return;}//根据当前blogId在数据库中进行查找,找到对应的blog对象,根据blog,找到作者信息BlogDao blogDao=new BlogDao();Blog blog=blogDao.selectOne(Integer.parseInt(param));if(blog==null){resp.getWriter().write("{\"ok\":false,\"reason\":\"要查询的博客不存在!\"}");return;}//根据blog,查询到用户对象UserDao userDao=new UserDao();User author=userDao.selectById(blog.getUserId());if(author==null){resp.getWriter().write("{\"ok\":false,\"reason\":\"要查询的用户不存在!\"}");return;}//把author返回给浏览器author.setPassword("");resp.getWriter().write(objectMapper.writeValueAsString(author));}}

3.编写服务器代码

blog_detail.html

  // 加上一个逻辑, 通过 GET /login 这个接口来获取下当前的登录状态~function getUserInfo(pageName) {$.ajax({type: 'get',url: 'login',success: function(body) {// 判定此处的 body 是不是一个有效的 user 对象(userId 是否非 0)if (body.userId && body.userId > 0) {// 登录成功!// 不做处理!console.log("当前用户登录成功! 用户名: " + body.username);// 在 getUserInfo 的回调函数中, 来调用获取作者信息getAuthorInfo(body);} else {// 登录失败!// 让前端页面, 跳转到 login.htmlalert("当前您尚未登录! 请登录后再访问博客列表!");location.assign('blog_login.html');}},error: function() {alert("当前您尚未登录! 请登录后再访问博客列表!");location.assign('blog_login.html');}});}// 判定用户的登录状态getUserInfo("blog_detail.html");// 从服务器获取一下当前博客的作者信息, 并显示到界面上. // 参数 user 就是刚才从服务器拿到的当前登录用户的信息function getAuthorInfo(user) {$.ajax({type: 'get',url: 'authorInfo' + location.search,success: function(body) {// 此处的 body, 就是服务器返回的 User 对象, 是文章的作者信息if (body.username) {// 如果响应中的 username 存在, 就把这个值设置到页面上. changeUserName(body.username);if (body.username == user.username) {// 作者和登录的用户是一个人, 则显示 "删除按钮"let navDiv = document.querySelector('.nav');let a = document.createElement('a');a.innerHTML = '删除';// 期望点击删除, 构造一个形如 blogDelete?blogId=6 这样的请求a.href = 'blogDelete' + location.search;navDiv.appendChild(a);}} else {console.log("获取作者信息失败! " + body.reason);}}});}function changeUserName(username) {let h3 = document.querySelector('.card>h3');h3.innerHTML = username;}</script>  

八:实现"注销功能"

点击注销后,在服务器取消登录状态,跳转到登录页面

注销按钮是a标签,点击会给服务器发送一个HTTP请求,发送的请求会告诉服务器要退出登录

服务器收到会话,判断是否为空,不为空删除信息,跳转到登录页面

1.约定前后端接口

请求

GET/logout

响应

HTTP/1.1 302

Location:login.html

2.编写服务器代码

用户有一个session,同时session有一个user属性~两者同时具备,才叫登录状态~~

注销,只要破坏掉上面的任意一个条件就行了~~

此处选择的是破坏第二个条件,把 user属性从session中删了.

user属性被删除后续读取的时候,得到的是null,因此会给客户端返回一个无效的User对象,客户端就认为是未登录状态

步骤

1.找到当前用户会话

2.会话为空,返回

3.不为空,把会话删除

4.跳转到登录页

package controller;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;/*** @author KYF* @create 2023-04-22*/
@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//先找到当前用户的会话HttpSession session=req.getSession(false);if(session==null){//用户没有登录,谈不上注销resp.getWriter().write("当前用户尚未登录!无法注销");return;}//把用户的会话中的信息删掉session.removeAttribute("user");resp.sendRedirect("blog_login.html");}
}

3.编写客户端代码

把博客列表页,博客详情页,博客编辑页中的导航栏的注销按钮的href属性都做出修改,改成logout这个路径

登录页没有注销按钮

<a href="logout">注销</a>

九.发布博客

在博客编辑页,当用户输入博客标题和正文后,点击发布,发起HTTP请求,会把博客数据提交到服务器,由服务器存储到数据库

1.约定前后端交互接口

请求:

POST/blog

Content-Type:application/x-www-form-urlencoded

title=这是标题&content=这是正文

响应:

HTTP/1.1 302

Location:blog_list.html

2.编写服务器代码

在BlogServlet中,添加doPost方法

读取请求中的标题和正文,构造Blog对象,插入数据库

步骤

1.得到会话,会话存在

2.得到用户,用户存在

3.从请求中,取出参数(博客的标题和正文)

4.构造blog对象,把当前的信息填进去(作者id就是当前提交这个博客的用户身份信息)

5.插入数据库中

6.重定向到博客列表页

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {HttpSession session=req.getSession(false);if(session==null){//当前用户未登录,不能提交博客resp.setContentType("text/html;charset=utf8");resp.getWriter().write("当前用户未登录,不能提交博客");return;}User user= (User)session.getAttribute("user");if(user==null){//当前用户未登录,不能提交博客resp.setContentType("text/html;charset=utf8");resp.getWriter().write("当前用户未登录,不能提交博客");return;}req.setCharacterEncoding("utf8");//从请求中,取出参数(博客的标题和正文)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对象,把当前的信息填进去,并插入数据库中//postTime和blogId不用手动指定,都是插入数据库的时候自动生成Blog blog=new Blog();blog.setTitle(title);blog.setContent(content);//作者id就是当前提交这个博客的用户身份信息blog.setUserId(user.getUserId());BlogDao blogDao=new BlogDao();blogDao.insert(blog);//重定向到博客列表页resp.sendRedirect("blog_list.html");
}

3.编写客户端代码

要用form表单,

1.加form表单

2.给标题的输入框加入name属性

3.发布按钮改成了input标签

4.创建一个隐藏的textarea为了后续的form提交

5.修改初始化代码

6.选择器调整,保证样式不丢失

<!-- 包裹整个博客编辑页内容的顶级容器 --><div class="blog-edit-container"><form action="blog" method="post" style="height:100%"><div class="title" ><input type="text" placeholder="在此处输入标题" name="title" id="title"><!-- <button>发表文章</button> --><input type="submit" value="发布文章" id="submit"></div><!-- 放置md编辑器 --><div id="editor"><!--为了form提交,创建一个textarea多行编辑框,借助这个编辑框实现表单提交  --><!-- 可以设置editor.md,让编辑器markdown内容同步保存到textarea中, 进行form提交--><textarea name="content" style="display:none"></textarea></div></form></div><script>// 初始化编辑器// 构造一个editor对象let editor = editormd("editor", {// 第二个参数是js对象// 这里的尺寸必须在这里设置. 设置样式会被 editormd 自动覆盖掉. width: "100%",// 设定编辑器高度height: "calc(100% - 50px)",// 编辑器中的初始内容markdown: "# 在这里写下一篇博客",// 指定 editor.md 依赖的插件路径path: "editor.md/lib/",//  editor.md会把用户在编辑器输入的内容同步保存到隐藏的textarea中saveHTMLToTextatea:true,});</script>

十.删除博客

只有自己能删除自己的博客(当前用户登录信息和博客作者信息相同,则删除)

删除博客,肯定不是随便删除的~~

约定,只有,自己能删除自己的博客,不能删除别人的博客!!!(此处咱们暂时不考虑管理员)

界面上的处理

在博客详情页这里,就去进行判定,

判定看当前这个博客的作者,是否就是登录的用户.如果是,就在导航栏里显示一个“删除按钮”

如果不是,就不显示删除按钮~~

在博客详情页这块,其实既从服务器获取了当前用户的登录信息又获取到了博客的作者信息

主要看这两个接口返回的用户信息是不是一样的?

1.约定前后端接口

请求:

GET/login_delete?blogId=1

响应:

删除成功:

HTTP/1.1 302

Location:blog_list.html

2.编写客户端代码

这是关键代码,要在第一个ajax的回调中

执行第二个ajax,才能保证两个ajax之间获取数据的顺序先后~~

getAuthorInfo(user)放到getUserInfo(pageName)中调用

步骤

1.得到导航栏的信息

2.得到标签a的信息

3.a显示为删除

4.点击删除, 构造一个形如 blogDelete?blogId=6 这样的请求

 // 加上一个逻辑, 通过 GET /login 这个接口来获取下当前的登录状态~function getUserInfo(pageName) {$.ajax({type: 'get',url: 'login',success: function(body) {// 判定此处的 body 是不是一个有效的 user 对象(userId 是否非 0)if (body.userId && body.userId > 0) {// 登录成功!// 不做处理!console.log("当前用户登录成功! 用户名: " + body.username);// 在 getUserInfo 的回调函数中, 来调用获取作者信息getAuthorInfo(body);} else {// 登录失败!// 让前端页面, 跳转到 login.htmlalert("当前您尚未登录! 请登录后再访问博客列表!");location.assign('blog_login.html');}},error: function() {alert("当前您尚未登录! 请登录后再访问博客列表!");location.assign('blog_login.html');}});}// 判定用户的登录状态getUserInfo("blog_detail.html");// 从服务器获取一下当前博客的作者信息, 并显示到界面上. // 参数 user 就是刚才从服务器拿到的当前登录用户的信息function getAuthorInfo(user) {$.ajax({type: 'get',url: 'authorInfo' + location.search,success: function(body) {// 此处的 body, 就是服务器返回的 User 对象, 是文章的作者信息if (body.username) {// 如果响应中的 username 存在, 就把这个值设置到页面上. changeUserName(body.username);if (body.username == user.username) {// 作者和登录的用户是一个人, 则显示 "删除按钮"let navDiv = document.querySelector('.nav');let a = document.createElement('a');a.innerHTML = '删除';// 期望点击删除, 构造一个形如 blogDelete?blogId=6 这样的请求a.href = 'blogDelete' + location.search;navDiv.appendChild(a);}} else {console.log("获取作者信息失败! " + body.reason);}}});}function changeUserName(username) {let h3 = document.querySelector('.card>h3');h3.innerHTML = username;}

3.编写服务器代码

请求:GET/blogDelete?blogId=7

响应:直接跳转到博客列表页

步骤

1.检查当前用户是否登录(会话和用户)

2.获取到参数中的blogId

3.获取要删除的博客信息

4.再次校验,当前的用户是否就是博客作者

5.确认无误,开始删除

6.重定向到博客列表页

package controller;import model.Blog;
import model.BlogDao;
import model.User;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;/*** @author KYF* @create 2023-04-23*/
@WebServlet("/blogDelete")
public class BlogDeleteServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {//1.检查当前用户是否登录HttpSession session=req.getSession(false);if(session==null){resp.setContentType("text/html;charset=utf8");resp.getWriter().write("当前未登录,不能删除");return;}User user= (User) session.getAttribute("user");if(user==null){resp.setContentType("text/html;charset=utf8");resp.getWriter().write("当前未登录,不能删除");return;}//2.获取到参数中的blogIdString blogId=req.getParameter("blogId");if(blogId==null || "".equals(blogId)){resp.setContentType("text/html;charset=utf8");resp.getWriter().write("当前blogId参数不对");return;}//3.获取要删除的博客信息BlogDao blogDao=new BlogDao();Blog blog=blogDao.selectOne(Integer.parseInt(blogId));if(blog==null){resp.setContentType("text/html;charset=utf8");resp.getWriter().write("当前要删除的博客不存在");return;}//4.再次校验,当前的用户是否就是博客作者if(user.getUserId()!=blog.getUserId()){resp.setContentType("text/html;charset=utf8");resp.getWriter().write("当前登录的用户不是作者,没有权限登录");return;}//5.确认无误,开始删除blogDao.delete(Integer.parseInt(blogId));//6.重定向到博客列表页resp.sendRedirect("blog_list.html");}
}

十一.项目部署

要求:用Linux部署博客系统

1.前期工作

1.1tomcat

1.2.Mysql

右键复制ssl渠道,打开一个新的xshell

mysql -uroot

插入dp.sql中信息

ctrl+d退出

2.代码微调

DBUtil-->数据库没有密码-->设置为空字符串

private static final String PASSWORD="";

netstat -anp显示所有网络信息

grep mysql筛选mysql相关的内容

3.打包

使用maven打包,打成war包

pom.xml中加入代码

<packaging>war</packaging>
<build><finalName>blog</finalName>
</build>

4.进行部署

1.把war拷贝到云服务器上的webapps目录

1.1找到打开的tomcat

打开webapps/

1.2把war包托入

2.启动tomcat服务器

进入bin目录启动startup.sh(要保证是绿色-->有执行权限)

如果不是绿色,要修改权限 chmod + * .sh(给所有sh文件都加权限)

启动成功

3.验证tomcat是否启动成功

博客系统[Java]相关推荐

  1. Java项目:实现个人博客系统(java+springboot+mybatis+redis+vue+elementui+Mysql)

    源码获取:博客首页 "资源" 里下载! springboot+mybatis+前端vue,使用前后端分离架构实现的个人博客系统,共7个模块,首页,写博客,博客详情页,评论管理,文章 ...

  2. Java项目:个人博客系统(java+SSM+Mysql+Servlet+JavaWeb)

    源码获取:博客首页 "资源" 里下载! 一.项目简述 项目内容包括:首页,登陆,新建文章,搜索,登陆日志,登录次数,评论统计,相关信息,文章列表等其他相关功能 另外:系统采用MVC ...

  3. 简单个人博客系统java web_JavaWeb个人博客项目:手把手教你实现博客后台系统之登录与注册...

    JavaWeb个人博客项目:手把手教你实现博客后台系统之登录与注册 发布时间:2020-07-17 17:10阅读:( )字号: 大 中 小 后台系统的所有界面图以及之前的准备工作欢迎看我之前的博文& ...

  4. java基于ssm的个人博客系统_一个基于 Spring Boot 的开源免费博客系统

    概况 mblog 开源免费的博客系统, Java 语言开发, 支持 mysql/h2 数据库, 采用 spring-boot.jpa.shiro.bootstrap 等流行框架开发.支持多用户, 支持 ...

  5. java好的博客_推荐5个万博爆款Java开源博客,是我目前用过最好用的博客系统

    1.OneBlog 一个简洁美观.功能强大并且自适应的Java博客,使用springboot开发,前端使用Bootstrap,支持移动端自适应,配有完备的前台和后台管理功能. 功能简介 多种编辑器.自 ...

  6. 有故事的程序员必看的六个开源博客系统 | Gitee项目推荐

    程序员除了需要具备写代码的能力,还要有写作能力,项目开发过程中需要清晰的文字记录.发布开源项目,如果想让更多人使用或贡献代码,也必须有一份生动又明了的 Readme 文档.程序员很热衷通过博客写作,不 ...

  7. Java项目:朴素风个人博客系统(前后端分离+java+vue+Springboot+ssm+mysql+maven+redis)

    源码获取:博客首页 "资源" 里下载! 一.项目简述 本系统功能包括: 基于vue + Springboo痼J后端分离项目个人博客系统,注册 登录,首页展示,喜爰图书展示,后台图书 ...

  8. Github 标星 11.5K!这可能是最好的 Java 博客系统

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 来源:github.com/halo-dev/halo 简介 快速 ...

  9. 如何快速部署国人开源的 Java 博客系统 Tale

    喜欢我们的文章?!欢迎大家关注腾讯云技术社区-简书主页哦~ 文末有彩蛋,不要错过! 除了闷头专研技术之外,程序员还需要不断地写作进行技术积累,写博客是其中最重要的方式之一.商业博客平台不少,但是更符合 ...

最新文章

  1. Excel百万数据导入oracle,excel表数据导入oracle的方法!(超级有用)
  2. vlc框架流程解析(转)
  3. mysql 跳表 b 树_简单谈谈Mysql索引与redis跳表
  4. 江山控股附属斥资3.02亿收购云阳新能源发电100%股权并偿债
  5. win7 其他用户当前已登录到此计算机,win7旗舰版的电脑如何切换至公共用户?-win7用户,win7账户已被停用按f8没反应...
  6. 分别用雅可比(Jacobi)迭代法和高斯—塞德尔(Gauss—Seidel)迭代法求解线性方程组(转载)
  7. activiti高亮显示图片_电气自动化控制中,工业显示器应用如何“硬核”拓展细分领域?...
  8. 发布时NSLog不打印信息
  9. 穷人的孩子真的早当家吗?
  10. 天云服务器做系统,自己做云服务器系统
  11. 利用DiskGenius对硬盘无损分区大小调整+无损分割新分区。
  12. STM32学习(电容触摸按键)
  13. 数字图像处理的技术方法和应用
  14. 使用java将word文档docx,doc(包含图形,文本框)完美转换成所有格式图片(pdf,png,gif,jpeg等等)
  15. PCIe设备在一个系统中是如何发现与访问的
  16. Echarts桑基图的排列顺序
  17. 电脑端使用文件微信聊天记录的本机/新机备份迁移
  18. C#蓝牙链接+传输文件
  19. ADRC——ESO扩张状态观测器simulink实现(含代码)
  20. [ web 漏洞篇 ] 常见web漏洞总结之 RCE 远程代码 / 命令执行漏洞总结

热门文章

  1. 测试显卡显存以及tensorflowGPU
  2. 【Fabric】- Fabric官网案例First-network
  3. Python基础——继承、多态
  4. android 播放器 sma,SMA测评软件下载-SMA测评安卓版v1.1.2-魅卓网
  5. 最短路径 floyd最小环 洛谷2738 篱笆回路 网上题解
  6. 时间序列模型 (二):移动平均法
  7. swf转Word小技巧
  8. 计算机排第三!新增的工学硕士博士学位
  9. UUID 通用唯一识别码(Universally Unique Identifier)介绍
  10. LINUX去掉“哔哔叫”的方法