一个简单的小项目 —— 我的博客系统

Posted 王嘻嘻-

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一个简单的小项目 —— 我的博客系统相关的知识,希望对你有一定的参考价值。

项目界面展示

博客登录页面

博客列表页面

博客正文页面

博客编辑页面

1. 创建 maven 项目

创建必要的目录,引入需要的依赖

2. 数据库设计

表设计

用户表

INSERT INTO `blog`.`users` (`username`, `password`, `avatar`, `git_repo`) 
VALUES ('嘻嘻', '123', 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fmobimg.b-cdn.net%2Fpic%2Fv2%2Fgallery%2Fpreview%2Foshki_oty_otiki-zhivotnye-47892.jpg&refer=http%3A%2F%2Fmobimg.b-cdn.net&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1658972360&t=20ee04839eff3c5cb622ca769e9499dd', 'https://gitee/wyc');

文章表

UPDATE `blog`.`articles` SET `uid` = '2', `title` = '追风筝的人', `type` = '散文', `published_at` = '2021-03-07 03:34:21', `content` = '只要你能为它而付出真心,它一定就在你所追寻的方向!' 
WHERE (`aid` = '3');

INSERT INTO  `blog`.`articles`( `uid`, `title`, `type` , `published_at` , `content` ) 
VALUES ( '2' ,'追风筝的人','散文','2021-03-07 03:34:21', '只要你能为它而付出真心,它一定就在你所追寻的方向!');

INSERT INTO  `blog`.`articles`( `uid`, `title`, `type` , `published_at` , `content` ) 
VALUES ( '1' ,'月亮与六便士','散文','2022-06-30 20:30:56', '满地都是六便士,他却抬头看见了月亮。');

封装数据库操作代码

创建 DBUtil
public class DBUtil 
    private static final DataSource dataSource;

    static 
        mysqlDataSource mysqlDataSource = new MysqlDataSource();
        mysqlDataSource.setUrl("jdbc:mysql:///blog?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai");
        mysqlDataSource.setUser("root");
        mysqlDataSource.setPassword("123456");
        dataSource = mysqlDataSource;
    

    @SneakyThrows
    public static Connection connection() 
        return dataSource.getConnection();
    

创建用户类和文章类

用户类 User —— 表示一个用户

public class User 
    public Integer uid;
    public String username;
    @JsonIgnore
    public String password;
    public String avatar;
    public String gitRepo;

文章类

@Data
public class Article 
    public Integer aid;
    @JsonIgnore
    public Integer uid;
    public String title;
    @JsonIgnore
    public String type;

    public String content;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    public Date publishedAt;

    public String getSummary() 
        int len = Integer.min(content.length(), 200);
        return content.substring(0, len);
    

创建 ArticleDao 类和 UserDao 类

理解 DAO DAO 全称为 “data access object”,主要的功能就是对于某个数据库表进行增删改查.
一般每张数据库表会对应一个 DAO 类. 这是一种给类命名的习惯做法, 并不是强制要求.

创建 ArticleDao 类, 针对博客表 articles 进行操作

insert: 插入一个 Article 对象到 articles 表中.

insert into articles (uid, title, type, published_at, content) values (?, ?, ?, ?, ?);

selectOne: 从 articles 表中查找指定的 Article 对象.

select aid, title, published_at, content from articles where aid = ?;

selectTypeCountByUid:从 articles 表中通过 uid 查找文章类型数量

select count(distinct type) from articles where uid = ?;

selectArticleCountByUid:从 articles 表中通过 uid 查找文章内容数量

select count(*) from articles where uid = ?;

创建 UserDao 类, 针对博客表 users 进行操作

注意: 登录的时候需要根据用户名验证密码。

selectOneByUsernameAndPassword:从 users 表中查找对应的用户名和密码

select uid, avatar, git_repo from users where username = ? and password = ?;

3. Junit 单元测试(用户管理和文章管理)

单元测试的编码规范
类名: 定义测试类,类名是由被测试类名Test构成。
包名: 定义的测试类需要放在xxx.xxx.xxx.test包中。
方法名: 测试方法的方法名有两种定义方式test测试方法和测试方法。
返回值: 因为我们的方法只是在类中测试,可以独立运行,所以不需要处理任何返回值,所以这里使用void。例如:public void insert();
参数列表: 因为我们的方法是用来测试的,至于参数列表的传入是没有必要的。我们在测试的时候自行传入需要的参数测试即可。所以在此参数列表为空。例如:例如:public void insert();
@Test注解: 测试是需要运行来完成的。如果我们只有一个main方法,显然在结构上还是需要我们去注释掉测试过的。解决此问题这里我们需要在测试方法上方加@Test注解来完成测试,只要是加该注解的方法,可以单独运行此方法来完成测试。
@Test注解jar包Junit4、5: @Test注解是需要我们导入jar包才能使用的。jar包有两个分别是:junit-4.13-rc-2和hamcrest-core-1.3
IDEA快捷导入Junit4、5: 用IDEA我们可以先创建测试类和方法,然后在测试方法上方加入@Test注解,此时IDEA显示的@Test注解是飘红的,这时候我们使用Alt + Enter组合键来打开导入Junit单元测试列表,然后再选择Junit4或者Junit5确定即可导入成功!这时候再查看注解就没有飘红了!

文章插入测试结果展示:

对用户表做了测试:查询用户名为 “hhh” 的用户信息

测试结果:符合预期结果(和数据库中信息数据一致)

4. 准备前端页面

拷贝页面
把之前写好的博客系统的静态页面拷贝到 webapp 目录中.

封装 ajax

在前后端交互中我们需要用到 ajax 进行数据交互.
我们把之前写过的 ajax 函数拷贝过来, 放到一个单独的 js 文件中, 方便后续使用.

前面的代码中我们基于模板的方式来开发了博客系统.
在基于模板方式中, 主要是通过服务器把数据渲染到页面中, 然后直接返回完整的页面给浏览器.
目前现在更主流的开发方式是 “前后端分离” 的方式. 这种方式下服务器端不关注页面的内容, 而只是给网页端提供数据.
网页端通过 ajax 的方式和服务器之间交互数据, 网页拿到数据之后再根据数据的内容渲染到页面上.

5. 实现博客主页界面设计

我们约定, 浏览器给服务器发送一个 GET /blog 这样的 HTTP 请求, 服务器给浏览器返回了一个 JSON 格式的数据.

实现服务器代码

创建ArticleDetailJsonServlet, , 放到 api 包中。实现 doGet, 完成读取博客列表的功能.

@WebServlet("/article-detail.json")
public class ArticleDetailJsonServlet extends HttpServlet 
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException, UnsupportedEncodingException 
        // 1. 从请求参数中获取 aid
        req.setCharacterEncoding("utf-8");
        String aidString = req.getParameter("aid");
        // TODO: 参数合法性问题
        int aid = Integer.parseInt(aidString);

        User currentUser = null;
        HttpSession session = req.getSession(false);
        if (session != null) 
            currentUser = (User) session.getAttribute("currentUser");
        

        // 构造一个结果对象
        ArticleService articleService = new ArticleService();
        ArticleDetailResult result = articleService.detail(currentUser, aid);

        // 把对象变成 JSON 字符串
        ObjectMapper objectMapper = new ObjectMapper();
        String jsonString = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(result);

        // 响应这个 jsonString
        resp.setCharacterEncoding("utf-8");
        resp.setContentType("application/json");
        resp.getWriter().println(jsonString);
    

实现客户端代码
  • 使用 ajax 给服务器发送 HTTP 请求.
  • 服务器返回的响应是一个 JSON 格式的数据, 根据这个响应数据使用 DOM API构造页面内容.
  • 列表页中拿到的 "content"字段其实是已经裁剪过的摘要.
  • 跳转到博客详情页的 url 形如 blog_content.html?blogId=1,这样就可以让博客详情页知道当前是要访问哪篇博客.
function renderAuthor(currentUser) 
    document.querySelector('.author-avatar').src = currentUser.avatar
    document.querySelector('.author-username').textContent = currentUser.username
    document.querySelector('.author-git').href = currentUser.gitRepo


function renderCount(articleCount, typeCount) 
    document.querySelector('.article-count').textContent = articleCount
    document.querySelector('.type-count').textContent = typeCount


function renderArticleList(articleList) 
    console.log(articleList)
    var container = document.querySelector('.articleList')
    console.log(container)
    for (var i in articleList) 
        var article = articleList[i]
        console.log(article)

        var html = `<div class="blog">` +
            `<div class="title">$article.title</div>` +
            `<div class="date">$article.publishedAt</div>` +
            `<div class="desc">$article.summary</div>` +
            `<a href="blog_content.html?aid=$article.aid" class="detail">查看全文 &gt;&gt;</a>` +
        `</div>`

        container.innerHTML += html
    


var xhr = new XMLHttpRequest()
//xhr.open('get', '/json/article_list.json')
xhr.open('get', '/article-list.json')
xhr.onload = function() 
    alert(this.responseText);
    var data = JSON.parse(this.responseText)

    if(!data.currentUser) 
       // redirect
       // location = '/login.html'
       location.assign('/login.html')
       return
    
    renderAuthor(data.currentUser)
    renderCount(data.articleCount, data.typeCount)
    renderArticleList(data.articleList)

xhr.send()

通过 URL 访问 http://127.0.0.1:8080/blog_list.html 访问服务器,验证效果

理解数据交互过程

在刚才的页面访问过程中, 涉及两次 HTTP 请求-响应 的交互. (不考虑从服务器下载 css, js, 图片等)

第一次请求: 浏览器从服务器下载 blog_list.html 页面.
第二次请求: blog_list.html 中触发了 ajax 请求, 获得到 博客列表 数据.

在前后端分离的模式中, 往往一个页面的显示需要多次 HTTP 交互过程.

6. 实现博客详情页面设计

目前点击博客列表页的 “查看全文” , 能进入博客详情页, 但是这个博客详情页是写死的内容. 我们期望能 够根据当前的博客 aid 从服务器动态获取博客内容.

function renderAuthor(user) 
    document.querySelector('.author-avatar').src = user.avatar
    document.querySelector('.author-name').textContent = user.username
    document.querySelector('.author-git').href = user.gitRepo


function renderCount(articleCount, typeCount) 
    document.querySelector('.article-count').textContent = articleCount
    document.querySelector('.type-count').textContent = typeCount


function renderArticle(article) 
    document.querySelector('.article-title').textContent = article.title
    document.querySelector('.date').textContent = article.publishedAt
    document.querySelector('.content').textContent = article.content


// 1. 请求一个 URL(返回 JSON 字符串)
var xhr = new XMLHttpRequest()
xhr.open('get', '/article-detail.json' + location.search)
xhr.onload = function () 
    alert(this.responseText)
    var data = JSON.parse(this.responseText)
    if (!data.currentUser) 
        location.assign('/login.html')
        return
    

    renderAuthor(data.currentUser)
    renderCount(data.articleCount, data.typeCount)
    renderArticle(data.article)

xhr.send()

登录后输入 http://127.0.0.1:8080/blog_content.html?aid=5 展示界面如下:

7. 总结

服务器渲染和客户端渲染(前后端分离) 都是常见的 web 开发的方式. 目前前后端分离的方式更主流一 些.
主要原因:

  • 前后端分离更便于分工协作: 开发开始时, 前端工程师和后端工程师共同约定好交互接口, 然后就可 以分别开发, 各自测试.直到最终双方开发完毕再在一起联调.
  • 网络带宽越来越大: 因此渲染一个页面多使用几个 HTTP 请求-响应 也问题不大.
  • 用户主机的计算能力越来越强: 无论是手机还是PC, 算力都在突飞猛进的增长. 因此这样的渲染工作对于客户端来说不是什么负担, 但是能降低服务器的负荷.
  • 更便于多端开发: 比如同一份服务器代码, 就既可以给网页端提供服务, 也可以给手机app 提供服务.

在前后端分离的模式下, 约定前后端交互接口是一件至关重要的事情. 约定的方式也有很多种. 其中一种比 较流行的方式称为 “Restful 风格”

  • 使用不同的 HTTP 方法, 表示要执行的动作. 例如 GET 用于获取数据, POST 用于新增数据, PUT 用于 修改数据, DELETE 用于删除数据.
  • 使用 URL 中的 PATH 表示要操作的资源.
  • 使用响应的状态码表示不同的响应结果.
  • 使用 JSON 格式作为 body 中的数据组织方式.

我们上面的代码模仿了 Restful 风格, 但是还不算特别严格. 比如我们在提交博客的时候不是使用 JSON 格式的数据.
实际开发的时候也不必完全拘泥于这样的格式. 都可以灵活对待.

以上是关于一个简单的小项目 —— 我的博客系统的主要内容,如果未能解决你的问题,请参考以下文章

一个简单的小项目 —— 我的博客系统

2017下半年的小目标

zookeeper基本讲解(Java版,真心不错)

价格帮助将英镑变成便士

VueNuxt服务端渲染,NodeJS全栈项目,面试小白的博客系统~~

python3开发进阶-Django框架学习前的小项目(一个简单的学员管理系统)