博客登录页面

博客列表页面

博客正文页面

博客编辑页面

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


用户表
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', '满地都是六便士,他却抬头看见了月亮。');
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);
}
}
理解 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 进行操作
注意: 登录的时候需要根据用户名验证密码。
- 1
selectOneByUsernameAndPassword:从 users 表中查找对应的用户名和密码
select uid, avatar, git_repo from users where username = ? and password = ?;
单元测试的编码规范
类名: 定义测试类,类名是由被测试类名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” 的用户信息

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

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

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

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

我们约定, 浏览器给服务器发送一个 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);
}
}
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 = `` +
`${article.title}` +
`${article.publishedAt}` +
`${article.summary}` +
`${article.aid}" class="detail">查看全文 >>` +
``
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 交互过程.
目前点击博客列表页的 “查看全文” , 能进入博客详情页, 但是这个博客详情页是写死的内容. 我们期望能 够根据当前的博客 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 展示界面如下:

服务器渲染和客户端渲染(前后端分离) 都是常见的 web 开发的方式. 目前前后端分离的方式更主流一 些.
主要原因:
在前后端分离的模式下, 约定前后端交互接口是一件至关重要的事情. 约定的方式也有很多种. 其中一种比 较流行的方式称为 “Restful 风格”
我们上面的代码模仿了 Restful 风格, 但是还不算特别严格. 比如我们在提交博客的时候不是使用 JSON 格式的数据.
实际开发的时候也不必完全拘泥于这样的格式. 都可以灵活对待.