Java EE初阶---博客系统(使用模板技术)

        前面的学习中, 我们基于 HTML, CSS, JavaScript 实现了一个简单的博客系统的页面。 接下来我们基于博客系统页面来实现一个带服务器版本的博客程序.

1、准备工作

1) 创建 web 项目
2) 创建目录结构

 3) 配置 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.example</groupId>
    <artifactId>博客系统(基于模板)</artifactId>
    <version>1.0-SNAPSHOT</version>
    <!-- 指定属性信息 -->
    <properties>
        <encoding>UTF-8</encoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    <dependencies>
        <!-- 加入 servlet 依赖 -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <!-- servlet 版本和 tomcat 版本有对应关系,切记 -->
            <version>3.1.0</version>
            <!-- 这个意思是我们只在开发阶段需要这个依赖,部署到 tomcat 上时就不需要了 -->
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.45</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.12.3</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.thymeleaf/thymeleaf -->
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf</artifactId>
            <version>3.0.12.RELEASE</version>
        </dependency>
    </dependencies>
    <packaging>war</packaging>
    <build>
        <!-- 指定最终 war 包的名称 -->
        <finalName>BlogSystem</finalName>
    </build>
</project>

2、数据库设计

2.1 表设计

当前需要设计两张表 , 文章表和用户表 .
  • 文章表
create table blog(blogId int primary key auto_increment,    -- 博客 id
                 title varchar(1024), -- 博客标题
                 content mediumtext, -- 博客正文
                 userId int, -- 博客作者 id
                 postTime datetime); -- 发布时间
  • 用户表
create table user(userId int primary key auto_increment,
                 username varchar(128),
                 password varchar(128));
完整 SQL
create database if not exists BlogSystem;
use BlogSystem;
drop table if exists blog;
create table blog(blogId int primary key auto_increment,    -- 博客 id
                 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),
                 password varchar(128));

2.2 封装数据库操作代码

  • 创建 DBUtil
代码和之前版本相同 . 通过一个单例类来获取数据库连接
public class DBUtil {
    private static final String URL = "jdbc:mysql://127.0.0.1:3306/BlogSystem?
characterEncoding=utf8&useSSL=false";
    private static final String USERNAME = "root";
    private static final String PASSWORD = "2222";
    private 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() {
        try {
            return getDataSource().getConnection();
       } catch (SQLException e) {
            e.printStackTrace();
       }
        return null;
   }
    public static void close(Connection connection,
                             PreparedStatement statement,
                             ResultSet resultSet) {
        try {
            if (resultSet != null) {
                resultSet.close();
           }
            if (statement != null) {
                statement.close();
           }
            if (connection != null) {
                connection.close();
           }
       } catch (SQLException e) {
            e.printStackTrace();
       }
   }
}
  • 创建 Blog 类 和 User
Blog 类表示一篇博客
public class Blog {
    private int blogId;
    private String title;
    private String content;
    private int userId;
    // 注意, 此处不能使用 java.sql.Date(这个类只有年月日, 没有时分秒)
    // 而应该使用 java.sql.TimeStamp
    private TimeStamp postTime;
    // getter / setter 略 }
User 类表示一个用户
public class User {
    private int userId;
    private String username;
    private String password;
    // getter / setter 略 }
  • 创建 BlogDao 类和 UserDao
理解 DAO
DAO 全称为 "data access object",主要的功能就是对于某个数据库表进行增删改查.
一般每张数据库表会对应一个 DAO 类. 这是一种给类命名的习惯做法, 并不是强制要求.
创建 BlogDao 类, 针对博客表进行操作
  1. insert: 插入一个 Blog 对象到 blog 表中.
  2. selectAll: 从 blog 表中查找所有的 Blog 对象.
  3. selectOne: 从 blog 表中查找指定的 Blog 对象.
  4. delete: blog 表中删除指定的 Blog 对象.
public class BlogDao {
    // 把一个博客对象插入到 blog 表中
    public void insert(Blog blog) {
   }
    // 从 blog 表中查找到所有的 blog 对象
    public List<Blog> selectAll() {
        return null;
   }
    // 从 blog 表中查找到指定的 blog 对象
    public Blog selectOne(int blogId) {
        return null;
   }
    // 删除指定的 blog 对象
    public void delete(int blogId) {
   }
}
1) 实现 BlogDao insert
public void insert(Blog blog) {
    Connection connection = DBUtil.getConnection();
    String sql = "insert into blog values(null, ?, ?, ?, ?)";
    PreparedStatement statement = null;
    try {
        statement = connection.prepareStatement(sql);
        statement.setString(1, blog.getTitle());
        statement.setString(2, blog.getContent());
        statement.setInt(3, blog.getUserId());
        statement.setTimeStamp(4, blog.getPostTime());
        statement.executeUpdate();
   } catch (SQLException e) {
        e.printStackTrace();
   } finally {
        DBUtil.close(connection, statement, null);
   }
}
2) 实现 BlogDao selectAll
public List<Blog> selectAll() {
    List<Blog> blogs = new ArrayList<>();
    Connection connection = DBUtil.getConnection();
    String sql = "select * from blog";
    PreparedStatement statement = null;
    ResultSet resultSet = null;
    try {
        statement = connection.prepareStatement(sql);
        resultSet = statement.executeQuery();
        while (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"));
            blogs.add(blog);
       }
        return blogs;
   } catch (SQLException e) {
        e.printStackTrace();
   } finally {
        DBUtil.close(connection, statement, resultSet);
   }
    return null; }
3) 实现 BlogDao selectOne
public Blog selectOne(int blogId) {
    Connection connection = DBUtil.getConnection();
    String sql = "select * from blog where blogId = ?";
    PreparedStatement statement = null;
    ResultSet resultSet = null;
    try {
        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) {
        e.printStackTrace();
   } finally {
        DBUtil.close(connection, statement, resultSet);
   }
    return null; }
4) 实现 BlogDao delete
public void delete(int blogId) {
    Connection connection = DBUtil.getConnection();
    String sql = "delete from blog where blogId = ?";
    PreparedStatement statement = null;
    try {
        statement = connection.prepareStatement(sql);
        statement.setInt(1, blogId);
        statement.executeUpdate();
   } catch (SQLException e) {
        e.printStackTrace();
   } finally {
        DBUtil.close(connection, statement, null);
   }
}
创建 UserDao , 实现对于用户表的增删改查 .
        注意: 登陆的时候需要根据用户名验证密码. 因此查找用户信息也是根据用户名查找.
public class UserDao {
    // 新增用户信息, 用于注册
    public void insert(User user) {
   }
    // 根据用户名查询 User 对象
    public User selectByName(String username) {
        return null;
   }
    
    // 根据用户 id 查询 User 对象    
    public User selectById(int userId) {
   return null;
   }
}
1) 实现 insert
public void insert(User user) {
    Connection connection = DBUtil.getConnection();
    String sql = "insert into user values(null, ?, ?)";
    PreparedStatement statement = null;
    try {
        statement = connection.prepareStatement(sql);
        statement.setString(1, user.getUsername());
        statement.setString(2, user.getPassword());
        statement.executeUpdate();
   } catch (SQLException e) {
        e.printStackTrace();
   } finally {
        DBUtil.close(connection, statement, null);
   }
}
2) 实现 selectByName
// 查询用户信息, 用于登陆
public User selectByName(String username) {
    Connection connection = DBUtil.getConnection();
    String sql = "select * from user where username = ?";
    PreparedStatement statement = null;
    ResultSet resultSet = null;
    try {
        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; }
3) 实现 selectById
// 根据用户 id 查询 User 对象
public User selectById(int userId) {
    Connection connection = DBUtil.getConnection();
    String sql = "select * from user where userId = ?";
    PreparedStatement statement = null;
    ResultSet resultSet = null;
    try {
        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; }

2.3 验证数据库代码

        创建 TestDB , 针对上面封装的功能进行简单的测试验证 .
测试博客表操作
public class TestDB {
    // 1. 验证插入
    public static void testInsert() {
        Blog blog = new Blog();
        blog.setTitle("我的第一篇博客");
        blog.setContent("这是博客的正文");
        blog.setUserId(1);
        // 基于当前的时间戳, 创建 java.sql.Date 对象
        blog.setPostTime(new Timestamp(System.currentTimeMillis()));
        BlogDao blogDao = new BlogDao();
        blogDao.insert(blog);
   }
    public static void testSelectAll() {
        BlogDao blogDao = new BlogDao();
        List<Blog> blogs = blogDao.selectAll();
        System.out.println(blogs);
   }
    public static void testSelectOne() {
        BlogDao blogDao = new BlogDao();
        Blog blog = blogDao.selectOne(1);
        System.out.println(blog);
   }
    public static void testDelete() {
        BlogDao blogDao = new BlogDao();
        blogDao.delete(1);
   }
    public static void testUserInsert() {
        User user = new User();
        user.setUsername("测试用户");
        user.setPassword("123");
        UserDao userDao = new UserDao();
        userDao.insert(user);
   }
    public static void testUserSelectByName() {
        UserDao userDao = new UserDao();
        User user = userDao.selectByName("测试用户");
        System.out.println(user);
   }
    
    public static void testUserSelectById() {
        UserDao userDao = new UserDao();
        User user = userDao.selectById(1);
        System.out.println(user);
   }
    
    public static void main(String[] args) {
 // ......
   }
}
运行程序 , 验证每个接口是否正确.

3、实现博客列表

暂时不考虑用户登陆.

3.1 初始化 TemplateEngine

创建 ThymeleafConfig 类.
代码和之前版本一致.
@WebListener
public class ThymeleafConfig implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext context = sce.getServletContext();
        TemplateEngine engine = new TemplateEngine();
        ServletContextTemplateResolver resolver = new
ServletContextTemplateResolver(context);
        resolver.setCharacterEncoding("utf-8");
        resolver.setPrefix("/WEB-INF/templates/");
        resolver.setSuffix(".html");
        engine.setTemplateResolver(resolver);
        context.setAttribute("engine", engine);
   }
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
   }
}

3.2 创建网页模板

将我们之前写好的博客系统的静态页面拷贝到 webapp 目录中.
拷贝完成的目录结构如下:
其中 blog_content.html 和 blog_list.html 需要借助服务器进行渲染. 放到 templates 目录中.
blog_edit.html 和 login.html 内容不需要服务器渲染, 直接放到 webapp 目录中.

修改 blog_list.html

  • 导航栏和个人信息部分代码都不变.
  • 把展示的博客列表, 替换成 ${blog.title} ${blog.content}
<!-- 导航栏 -->
<div class="nav">
 ... 导航栏代码不变
</div>
<!-- 版心 -->
<div class="container">
    <!-- 左侧个人信息 -->
    <div class="container-left">
 ... 个人信息代码不变
    </div>
    <!-- 右侧内容详情 -->
    <div class="container-right">
        <!-- 每一篇博客包含标题, 摘要, 时间 -->
        <div class="blog" th:each="blog : ${blogs}">
            <div class="title" th:text="${blog.title}">我的第一篇博客</div>
            <div class="date" th:text="${blog.postTime}">2021-06-02</div>
            <div class="desc" th:text="${blog.content}">
               从今天起, 我要认真敲代码. Lorem ipsum, dolor sit amet consectetur 
adipisicing elit. Cum distinctio ullam eum ut
               veroab laborum numquam tenetur est in dolorum a sint, assumenda 
adipisci similique quaerat vel.
               Facere,
               et.
            </div>
            <a href="blog_content.html?blogId=1" class="detail"
th:href="${'blog_content.html?blogId=' + blog.blogId}">查看全文 &gt;&gt;</a>
        </div>
    </div>
</div>

3.3 创建 BlogListServlet

  • @WebServlet 中的路径为 "/blog_list.html", 注意此时通过 URL http://127.0.0.1:8080/BlogSystem/blog_list.html 访问的时候, 访问的是一个动态页面,而不再是一个静态的 HTML.
@WebServlet("/blog_list.html")
public class BlogListServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
        resp.setContentType("text/html; charset=utf-8");
        // 1. 从数据库中查询博客数据
        BlogDao blogDao = new BlogDao();
        List<Blog> blogs = blogDao.selectAll();
        // 2. 通过模板引擎渲染页面
        ServletContext context = getServletContext();
        WebContext webContext = new WebContext(req, resp, context);
        webContext.setVariable("blogs", blogs);
        TemplateEngine engine = (TemplateEngine) context.getAttribute("engine");
        String html = engine.process("blog_list", webContext);
        resp.getWriter().write(html);
   }
}
        此时发现, 如果博客正文内容太长, 就会比较丑. 我们期望在博客列表页只显示一个博客内容的摘要即可.
形如:

 修改 BlogDao.selectAll 方法, 对查询到的 content 进行字符串截取. (其他代码不变)

public List<Blog> selectAll() {
    // ......其他代码不变
    String content = resultSet.getString("content");
    if (content.length() > 90) {
        content = content.substring(0, 90) + "...";
   }
    blog.setContent(content);
    // ......其他代码不变
}
此时效果形如 :

为啥是按照 90 个字符截取?

注意! 90 这个数字纯属拍脑门出来的. 只要让最终的界面显示的符合自己的审美就 OK 了~~

4、实现博客详情

4.1 创建网页模板

修改 blog_content.html
  • 导航栏和个人信息代码不变.
  • 把博客详情, 替换成 ${blog.title} ${blog.content} 等.
  • 之前使用 p 标签 表示正文, 现在直接把正文放到一个 <div id="content"></div> 中.
<!-- 右侧内容详情 -->
<div class="container-right">
    <div class="blog-content">
        <!-- 博客标题 -->
        <h3 th:text="${blog.title}">我的第一篇博客</h3>
        <!-- 博客时间 -->
        <div class="date" th:text="${blog.postTime}">2021-06-02</div>
        <!-- 博客正文 -->
        <div id="content" th:text="${blog.content}"></div>
    </div>
</div>

4.2 创建 BlogContentServlet

@WebServlet("/blog_content.html")
public class BlogContentServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
        resp.setContentType("text/html; charset=utf-8");
        // 1. 从请求中读取 blogId
        String blogId = req.getParameter("blogId");
        if (blogId == null || "".equals(blogId)) {
            String html = "<h3>blogId 字段填写错误!</h3>";
            resp.getWriter().write(html);
            return;
       }
        // 2. 根据 blogId 从数据库读取博客详情
        BlogDao blogDao = new BlogDao();
        Blog blog = blogDao.selectOne(Integer.parseInt(blogId));
        // 3. 根据 blog 对象渲染页面
        ServletContext context = getServletContext();
        WebContext webContext = new WebContext(req, resp, context);
        webContext.setVariable("blog", blog);
        TemplateEngine engine = (TemplateEngine) context.getAttribute("engine");
        String html = engine.process("blog_content", webContext);
        resp.getWriter().write(html);
   }
}
部署程序 , 执行效果如下

注意: 我们预期博客内容是 markdown 格式的内容, 而不是纯文本的内容. 当前还不能正确显示markdown 格式的数据. 这个我们后面再改进.

5、实现登陆

5.1 修改 login.html

login.html中的内容不需要服务器动态渲染。但是为了和服务器端进行交互, 我们需要引入form标签
  • 在 输入框 和 按钮 外面套上一个 form 标签.
  • 把用户名和密码两个输入框加上 name 属性
  • 把按钮从 button 标签改成 <input type="submit">
<!-- 中间的登陆框 -->
<div class="login-dialog">
    <h3>登陆</h3>
    <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>

5.2 创建 LoginServlet

实现登陆逻辑.
  • 此处使用 doPost 来处理.
  • 从 form 表单读取数据的时候别忘了 req.setCharacterEncoding("utf-8");
  • 根据用户提交的用户名, 从数据库读取 User 对象, 并验证密码是否正确.
  • 如果登陆成功, 则把该用户信息保存到 HttpSession 对象中.
  • 最后重定向到博客列表页
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
        req.setCharacterEncoding("utf-8");
        resp.setContentType("text/html; charset=utf-8");
        // 1. 读取用户名和密码
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        if (username == null || password == null || "".equals(username) ||
"".equals(password)) {
            String html = "<h3>登陆失败! 缺少 username 或 password 字段</h3>";
            resp.getWriter().write(html);
            return;
       }
        // 2. 在数据库中验证用户名密码
        UserDao userDao = new UserDao();
        User user = userDao.selectByName(username);
        if (!password.equals(user.getPassword())) {
            String html = "<h3>登陆失败! 用户名或者密码错误!</h3>";
            resp.getWriter().write(html);
            return;
       }
        // 3. 登陆成功, 设置 Session
        HttpSession session = req.getSession(true);
        session.setAttribute("user", user);
        // 4. 重定向到博客列表页.
        resp.sendRedirect("blog_list.html");
   }
}
部署程序 , 使用 URL http://127.0.0.1:8080/BlogSystem/login.html 访问页面

输入一个用户名和密码, 验证登陆效果.

6、实现强制要求登陆

当用户访问 博客列表页 和 博客详情页 时 , 如果用户当前尚未登陆 , 就自动跳转到登陆页面 .

6.1 创建 Util

实现 Util.checkLoginStatus 方法 , 用户检测当前用户的登陆状态 .
public class Util {
    public static User checkLoginStatus(HttpServletRequest req) {
        HttpSession session = req.getSession(false);
        if (session == null) {
            return null;
       }
        User user = (User) session.getAttribute("user");
        return user;
   }
}

6.2 修改 BlogListServlet BlogContentServlet

        在 doGet 方法的开头 , 调用 checkLoginStatus 判定当前用户的登陆状态 . 如果未登陆 , 则直接重定向到登陆页面.
  • 修改 BlogListServlet
@WebServlet("/blog_list.html")
public class BlogListServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
        resp.setContentType("text/html; charset=utf-8");
        // 1. 检测用户登陆状态
        User user = Util.checkLoginStatus(req);
        if (user == null) {
            System.out.println("当前用户尚未登陆!");
            resp.sendRedirect("login.html");
            return;
       }
 // ... 其他代码不变. 
   }
}
  • 修改 BlogContentServlet
@WebServlet("/blog_content.html")
public class BlogContentServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
        resp.setContentType("text/html; charset=utf-8");
        // 1. 验证登陆状态
        User user = Util.checkLoginStatus(req);
        if (user == null) {
            System.out.println("当前用户尚未登陆!");
            resp.sendRedirect("login.html");
            return;
       }
 // ... 其他代码不变
   }
}
重新部署程序, 使用 URL http://127.0.0.1:8080/BlogSystem/blog_list.html 和
http://127.0.0.1:8080/BlogSystem/blog_content.html 验证.

7、实现显示用户信息

目前页面的用户信息部分是写死的. 形如:

我们期望这个信息可以随着用户登陆而发生改变.

  • 如果当前页面是博客列表页, 则显示当前登陆用户的信息.
  • 如果当前页面是博客详情页, 则显示该博客的作者用户信息.
注意: 当前我们只是实现了显示用户名, 没有实现显示用户的头像以及文章数量等信息.

7.1 修改博客列表页

1) 修改 blog_list.html
  • 修改用户信息展示部分 <h3 th:text="user.username">比特汤老湿</h3> , 其他部分不变.
  • 预期在渲染模板的时候把 user 对象传到 模板 中.
<!-- 左侧个人信息 -->
<div class="container-left">
    <div class="card">
        <img src="img/doge.jpg" class="avtar" alt="">
        <h3 th:text="${user.username}">比特汤老湿</h3>
        <a href="http://www.github.com">github 地址</a>
        <div class="counter">
            <span>文章</span>
            <span>分类</span>
        </div>
        <div class="counter">
            <span>2</span>
            <span>1</span>
        </div>
    </div>
</div>
2) 修改 BlogListServlet
新增以下代码 : WebContext 中新增 user 字段 .
webContext.setVariable("user", user);
运行程序 , 验证效果 .

7.2 修改博客详情页

1) 修改 blog_content.html
  • 修改用户信息展示部分 <h3 th:text="user.username">比特汤老湿</h3> , 其他部分不变.
  • 预期在渲染模板的时候把 user 对象传到 模板 中.
<!-- 左侧个人信息 -->
<div class="container-left">
    <div class="card">
        <img src="img/doge.jpg" class="avtar" alt="">
        <h3 th:text="${user.username}">比特汤老湿</h3>
        <a href="http://www.github.com">github 地址</a>
        <div class="counter">
            <span>文章</span>
            <span>分类</span>
        </div>
        <div class="counter">
            <span>2</span>
            <span>1</span>
        </div>
    </div>
</div>
2) 修改 BlogContentServlet
  • 根据当前文章中的 userId, 查询对应的作者信息, 把作者信息设置到 WebContext .
@WebServlet("/blog_content.html")
public class BlogContentServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
        // ... 代码不变
 // 根据当前 blog 的 userId 查询 User 对象
        UserDao userDao = new UserDao();
        User author = userDao.selectById(blog.getUserId());
        webContext.setVariable("user", author);
        // ... 代码不变
   }
}
运行程序 , 验证效果 .

8、实现注销登陆

        注销操作其实就是把服务器中的 Session 对象清除掉。 在前面的页面的导航栏中, 已经包含了一个 " 注销 " a 标签 , 并且点击后会访问 "logout" 路径 . 形如 :
<a href="logout">注销</a>
接下来在服务器端实现对应的代码来处理即可 .

8.1 创建 LogoutServlet

@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
        resp.setContentType("text/html; charset=utf-8");
        // 1. 检测当前用户是否已经登陆
        HttpSession session = req.getSession(false);
        if (session == null) {
            String html = "<h3>当前尚未登陆, 无法注销!</h3>";
            resp.getWriter().write(html);
            return;
       }
        User user = (User) session.getAttribute("user");
        if (user == null) {
            String html = "<h3>当前尚未登陆, 无法注销!</h3>";
            resp.getWriter().write(html);
            return;
       }
        // 2. 删除 session 中的 user 属性
        session.removeAttribute("user");
        // 3. 重定向到登陆页面
        resp.sendRedirect("login.html");
   }
}
部署程序 , 验证注销效果 .

9、实现发布博客

9.1 给编辑页加入 form 表单

修改 blog_edit.html 页面结构 ,
  • 增加 form 标签, action blog_edit , method POST
  • form 指定 height: 100%; 防止编辑器高度不能正确展开.
  • 给标题的 input 标签加上 name 属性
  • 把提交按钮改成 <input type="submit" value="发布文章">
  • <div id="editor"> 里面加上一个隐藏的 textarea
<!-- 编辑框容器 -->
<div class="blog-edit-container">
    <form action="blog_edit" method="POST" style="height: 100%;">
        <!-- 标题编辑区 -->
        <div class="title">
            <input type="text" placeholder="在这里写下文章标题" id="title"
name="title">
            <input type="submit" id="submit" value="发布文章"></input>
        </div>
    <!-- 创建编辑器标签 -->
    <div id="editor">
        <textarea name="content" style="display: none;"></textarea>
    </div>
    </form>
</div>
  • editor.md 的初始化代码中, 新增一个选项 saveHTMLToTextarea: true
// 初始化编辑器
var editor = editormd("editor", {
    // 这里的尺寸必须在这里设置. 设置样式会被 editormd 自动覆盖掉. 
    width: "100%",
    // 高度 100% 意思是和父元素一样高. 要在父元素的基础上去掉标题编辑区的高度
    height: "calc(100% - 50px)",
    // 编辑器中的初始内容
    markdown: "# 在这里写下一篇博客",
    // 指定 editor.md 依赖的插件路径
    path: "editor.md/lib/",
    // 加上这个属性使 编辑器 的内容能保存到用户自己添加的 textarea 中. 
    saveHTMLToTextarea: true,
});
此时点击 " 发布文章 " 按钮就能发送一个 POST 请求给服务器 .

9.2 创建 BlogEditServlet

当点击提交的时候会给服务器 blog_edit 这个路径发送一个 POST 请求 , 接下来就来处理这个请求 .
@WebServlet("/blog_edit")
public class BlogEditServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
        req.setCharacterEncoding("utf-8");
        resp.setContentType("text/html; charset=utf-8");
        // 1. 验证是否已经登陆
        User user = Util.checkLoginStatus(req);
        if (user == null) {
            String html = "<h3>当前未登录, 不能新增博客</h3>";
            resp.getWriter().write(html);
            return;
       }
        // 2. 获取到提交的博客数据
        String title = req.getParameter("title");
        String content = req.getParameter("content");
        if (title == null || content == null || "".equals(title) ||
"".equals(content)) {
            String html = "<h3>title 或者 content 字段缺失! 新增博客失败!</h3>";
            resp.getWriter().write(html);
            return;
       }
        // 3. 构造博客对象
        Blog blog = new Blog();
        blog.setTitle(title);
        blog.setContent(content);
        blog.setUserId(user.getUserId());
        blog.setPostTime(new Timestamp(System.currentTimeMillis()));
        // 4. 把博客对象插入到数据库
        BlogDao blogDao = new BlogDao();
        blogDao.insert(blog);
        // 5. 重定向到博客列表页
        resp.sendRedirect("blog_list.html");
   }
}
部署程序 , 验证效果 .

9.3 支持 markdown 语法

        当我们提交了一个博客之后, 发现博客详情页中显示的是 markdown 的原始内容, 我们更希望显示成markdown 渲染后的内容。形如:

此处的 # 为一级标题, 但是此处并没有渲染成一级标题的样子.

修改 blog_content.html
  • 使用 editor.md 提供的 editormd.markdownToHTML 方法把 markdown 格式的数据转成 HTML
  • editormd.markdownToHTML 的详细用法可以参考 editor.md/examples/html-preview-markdown-to-html.html
  • #content 加上一个 style="background-color: transparent;" 这样可以使渲染出来的markdown 也像父元素一样半透明显示.
<!-- 引入 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>
<!-- 右侧内容详情 -->
<div class="container-right">
    <div class="blog-content">
        <!-- 博客标题 -->
        <h3 th:text="${blog.title}">我的第一篇博客</h3>
        <!-- 博客时间 -->
        <div class="date" th:text="${blog.postTime}">2021-06-02</div>
        <!-- 博客正文 -->
        <div id="content" style="background-color: transparent;"
th:text="${blog.content}"></div>
    </div>
</div>
// 把页面中的 #content 的内容按照 markdown 的格式进行解析
function renderMD() {
    var markdown = document.querySelector("#content").innerHTML;
    document.querySelector("#content").innerHTML = "";
    // 这个函数能够把 markdown 格式的数据渲染成 HTML. 
    // 第一个参数表示把数据放到 id 为 "#content" 的标签中. 显示之前需要先清空之前的 html 
内容. 
    editormd.markdownToHTML('content', { markdown: markdown });
}
renderMD();
部署程序 , 验证效果 .

10、实现删除博客

        进入用户详情页时, 如果当前登陆用户正是文章作者 , 则在导航栏中显示 " 删除 " 按钮 , 用户点击时则删除该文章.

10.1 修改 blog_content.html

在导航栏代码中新增一个 a 标签 , " 主页 " " 写博客 " " 注销 " 并列 .
<a th:if="${showDeleteButton}" th:href="${'blog_delete?blogId=' + blog.blogId}">
删除</a>

10.2 修改 BlogContentServlet

根据当前登陆用户的 userId 和文章作者的 userId, 判定该页面是否显示删除按钮 , 并把结果通过
showDeleteButton 传给页面
// 根据登陆用户的 userId 和文章作者的 userId, 决定是否要显示删除按钮
webContext.setVariable("showDeleteButton", user.getUserId() ==
author.getUserId());

10.3 创建 BlogDeleteServlet

处理删除逻辑
@WebServlet("/blog_delete")
public class BlogDeleteServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
        // 1. 验证用户是否登陆
        User user = Util.checkLoginStatus(req);
        if (user == null) {
            String html = "<h3>当前未登陆, 不能删除</h3>";
            resp.getWriter().write(html);
            return;
       }
        // 2. 从请求中获取到要删除的博客 id
        String blogId = req.getParameter("blogId");
        if (blogId == null) {
            String html = "<h3>blogId 参数错误!</h3>";
            resp.getWriter().write(html);
            return;
       }
        // 3. 从数据库中删除博客
        BlogDao blogDao = new BlogDao();
        blogDao.delete(Integer.parseInt(blogId));
        // 4. 重定向到博客列表页
        resp.sendRedirect("blog_list.html");
   }
}
部署程序 , 验证效果 .

11、整理代码结构

        此时我们的基本功能已经实现的差不多了. 但是我们也发现 , 此时的类已经比较多了 , 都放在一起 , 显的非常混乱.

此时我们可以按照类的功能的不同, 把这些类放到不同的包中.

整理之后的效果

  • api : 前后端交互的接口. 里面放了各种 Servlet .
  • common: 一些公共的代码.
  • dao: 操作数据的相关类.

12、其他功能

12.1 实现文章数目统计

提示:根据当前用户, 查询该用户有多少文章 , 显示在博客列表页和博客详情页的左侧用户信息区即可 .

12.2 实现编辑博客

提示 :
  • 给博客详情页中的导航栏中增加 "编辑按钮", 也是一个 a 标签, href blogUpdate?blogId=1 , 如果当前登陆的用户和文章作者相同, 则显示编辑按钮.
  • 创建 BlogUpdateServlet. 点击 "编辑按钮" 进入访问该 Servlet doGet 方法, 并返回一个博客编 辑页面. 这个页面基于 blog_edit.html 修改, 使编辑页面中的编辑框内已经显示出原来文章的内容.
  • 点击 "提交按钮" 则访问 BlogUpdateServlet doPost 方法, 此时根据请求中提交的新的博客标题和正文, 修改数据库的内容.
  • BlogDao 新增一个 update 方法, 用于修改数据库中的博客数据.

12.3 实现头像管理

提示 :
  • 在数据库的 User 表中, 新增一列, 表示该用户的头像图片的地址, 形如 avtar/1.jpg . 使用 alter table 可以修改数据库表结构(同学们自行查找资料, 学习 alter table 的用法).
  • 在博客列表页中, 点击用户头像, 则弹出对话框要求用户选择一张图片. 此时需要使用一个 form 签把头像部分包裹起来.
  • 服务器端创建一个 Servlet , 用来处理用户上传头像的请求, 把用户上传的图片保存到 webapps/avator 目录中. 同时修改数据库 User 表中的头像列为新的头像文件名.
  • 修改 blog_list.html, blog_content.html, BlogListServlet, BlogContentServlet 使博客列表页和详情页能正确显示用户头像.

12.4 实现用户 github 链接管理

提示 :
  • 在数据库的 User 表中, 新增一列, 表示该用户 github 主页. 使用 alter table 可以修改数据库表结构(同学们自行查找资料, 学习 alter table 的用法).
  • 在博客列表页中, 如果用户当前的 github 为空, 则显示一个 a 标签, 提示 "设置 github 链接". 点击后跳转到一个新的页面, 新页面中包含一个 form 用来输入 github 链接.
  • 在服务端创建一个 Servlet 来处理 github 链接设置的请求.

12.5 实现文章分类管理

提示 :
  • 在数据库中创建一个分类表, 包含 id, 名称, 所属用户id 三列.
  • blog 表新增一列, 表示该博客的 分类 id (每个文章只能属于一个分类, 每个分类可以包含多个文)
  • 在用户新增博客的时候, 让用户在页面中指定当前文章的分类名, 并在提交的时候, 由服务器把分类名转成分类id, 并保存到 blog 表中.
  • 在显示博客的页面中显示该博客所属的分类.
  • 在左侧用户信息显示区域, 显示出当前用户所拥有的分类的数量(从数据库中统计)
  • 点击该数字, 进入分类详细页, 页面中能显示当前一共都有哪些分类(实现一个新的 Servlet 实现). 击具体分类能够展示该分类下的博客列表(复用 blog_list.html 的代码, 但是需要给 BlogDao 新增方, 按照分类来查找博客列表)

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值