5. ブログ詳細ページ
ブログ一覧ページを書いた後、「全文へジャンプ」をクリックすると、表示される文章がテスト中に書いた内容です。
「全文にジャンプ」をクリックします: 127.0.0.1:8080/blog_system/ blog_detail.html?blogId=5
これは、ブログの詳細ページを取得するために送信されたリクエストです。ここで取得したいページには、現在のブログのメイン コンテンツが表示されます~~
このテキスト コンテンツは引き続きajax
取得され、blog_detail.html ページが読み込まれると、ajax リクエストがトリガーされてサーバーにアクセスし、取得されたブログ コンテンツが再びブログ詳細ページに入力されます。
1. インタラクティブインターフェースに同意する
聞く:
GET /blog?blogId=1
応答:
HTTP/1.1 200 OK
Content-Type:application/json;
{
blogId: 1,
title: "第一篇博客",
content: "这个正文",
userId: 1,
postTime: '2022-05-25' 13:33:33'
}
ブログリストの取得との違い:
-
リクエストにはパラメータblogldがあります
-
応答結果は json 配列ではなく、単一のオブジェクトです
-
レスポンシブなブログのテキストが切り詰められなくなりました。
2. BlogServlet を変更する
バックエンドコードの実装は基本的にブログ一覧ページの取得と同じなのでメソッドに直接実装できます。blogld パラメータを使用して、ブログリストを取得するか詳細を取得するかを区別します~
パラメータなしでブログリストリクエスト GET /blog を取得します。
// 通过这个类, 来处理 /blog 路径对应的请求
@WebServlet("/blog")
public class BlogServlet extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();
// 这个方法用来获取到数据库中的博客列表.
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("application/json; charset=utf8");
// 先尝试获取到 req 中的 blogId 参数. 如果该参数存在, 说明是要请求博客详情
// 如果该参数不存在, 说明是要请求博客的列表.
BlogDao blogDao = new BlogDao();
String param = req.getParameter("blogId");
if (param == null) {
List<Blog> blogs = blogDao.selectAll();
// 把 blogs 对象转成 JSON 格式.
String respJson = objectMapper.writeValueAsString(blogs);
// 构造响应的时候 这里的这两行代码 顺序不能颠倒.
// 如果先 write 了 body, 再设置 ContentType,设置的 ContentType就会不生效!!
// 包括说 即使不是写 body,构造 sendRedirect 这种, 其实也是类似的情况~~
resp.getWriter().write(respJson);
} else {
// 有 blogId,获取博客详情
int blogId = Integer.parseInt(param);
Blog blog = blogDao.selectOne(blogId);
String respJson = objectMapper.writeValueAsString(blog);
resp.getWriter().write(respJson);
}
}
}
Postman がリクエスト テストを構築します: 127.0.0.1:8080/blog_system/blog?blogId=3
body: blogId に基づいて取得されたブログの詳細
{
"blogId": 3,
"title": "这是第三篇博客",
"content": "从前天开始, 我要认真学 Java",
"userId": 1,
"postTime": "2022-51-27 02:51:36"
}
3. フロントエンドコード blog_detail.html
1). 右側の内容を変更します
blog_detail.html を変更して、このページがロードされたときに上記のインターフェイスを呼び出してサーバーからブログ データを取得できるようにします。
<!-- 右侧内容详情 -->
<div class="right">
<!-- 包裹整个博客的内容详情 -->
<div class="blog-content">
<!-- 博客标题 -->
<h3></h3>
<!-- 博客日期 -->
<div class="date"></div>
<!-- 博客的正文内容 -->
<div id="content">
</div>
</div>
</div>
2). 以下に ajax を追加します
フロントエンド コード側で、ブログの詳細を取得するリクエストを作成したい場合は、現在のユーザーがクリックしたブログの ID を知る必要があります!!
この ID は、現在の blog_detail.html の URL にすでに含まれています。ページ!!
質問 1: blogId を取得する方法:location.search
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<script>
function getBlogDetail() {
$.ajax({
type: 'get',
// location.search 拿到了形如 '?blogId=5' 这样的一段内容
url: 'blog' + location.search,
success: function(body) {
// body 中就是一个 js 对象
// 根据 body 中的内容来构造页面
// 1. 构造博客标题
let h3 = document.querySelector('.blog-content>h3');
h3.innerHTML = body.title;
// 2. 构造博客发布时间
let dateDiv = document.querySelector('.date');
dateDiv.innerHTML = body.postTime;
// 3. 构造博客正文
let contentDiv = document.querySelector('#content');
contentDiv.innerHTML = body.content;
}
});
}
getBlogDetail();
</script>
質問 2:現在のブログのテキストは単なるテキストではありません!! 実は、特定の書式を備えたマークダウン データです!!
コードで上記のロジックを使用し、コンテンツを innerHTML に直接設定すると、インターフェースの最終的な表示効果が左側にあり、必要な効果は右側のフォーマットであることを意味します。
この場合、どのように対処すればよいでしょうか? ここのマークダウン テキスト コンテンツを特定のスタイルの HTML フラグメントにレンダリングできますか? これを完了するには、やはりeditor.md
このライブラリを使用する必要があります
。このライブラリは、マークダウン エディターを提供するだけでなく、レンダリング機能を提供します。 ~~
完全なコード:
<!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/common.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="image/picture.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="image/picture.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 class="blog-content">
<!-- 博客标题 -->
<h3></h3>
<!-- 博客日期 -->
<div class="date"></div>
<!-- 博客的正文内容 -->
<div id="content" style="opacity: 73%;">
</div>
</div>
</div>
</div>
<script>
function getBlogDetail() {
$.ajax({
type: 'get',
// location.search 拿到了形如 '?blogId=5' 这样的一段内容
url: 'blog' + location.search,
success: function(body) {
// body 中就是一个 js 对象
// 根据 body 中的内容来构造页面
// 1. 构造博客标题
let h3 = document.querySelector('.blog-content>h3');
h3.innerHTML = body.title;
// 2. 构造博客发布时间
let dateDiv = document.querySelector('.date');
dateDiv.innerHTML = body.postTime;
// 3. 构造博客正文
// let contentDiv = document.querySelector('#content');
// contentDiv.innerHTML = body.content;
// 如果直接把 content 设为 innerHTML, 此时展示在界面上的内容, 是原始的 markdown 字符串
// 咱们需要的是渲染后的, 带有格式的效果
// 第一个参数对应 id=content 的 html 标签. 渲染后得到的 html 片段就会被放到这个 标签下.
editormd.markdownToHTML('content', {
markdown: body.content
});
}
});
}
getBlogDetail();
</script>
</body>
</html>
4.——検証
リスト ページにアクセスし、[全文を表示] をクリックすると、現時点ではページが変更されていないことがわかります。
注:プログラムの検証を実行するときは、ブラウザのキャッシュが結果に影響を与える可能性があることに常に留意してください。blog_detail
ページは以前にアクセスされているため、ブラウザはこのページをローカルに保存し、次回アクセスしようとする可能性があります。ローカル コンテンツに直接アクセスするだけです~ ~
SQLを追加します。
insert into blog values(null, '这是第三篇博客', '# 一级标题\n ### 三级标题\n > 这是引用内容', 2, now());
全文をクリックすると、ブログページはすべてマークダウン形式で表示されます
6. ログインページ
1. インタラクティブインターフェースに同意する
リクエスト:ここのロジックはフォームフォームを使用して直接送信できます。ajax を使用する必要はありません (ajax を使用しても問題ありません!!)
フォームを使用したい場合は、ボタンを次のように変更する必要があります。input type="submit"
POST /login
Content-Type:application/x-www-form-urlencoded
応答:
HTTP/1.1 302
Location:blog_list_html
ログインに成功すると自動的にトップページ(ブログ一覧ページ)にジャンプします
2. フロントエンドコード blog_login.html
今回は最初にクライアントを作成し、ログインページを少し調整します~~
ログインコンテナを変更します。
-
form
ログイン ダイアログ コードにタグのレイヤーを追加します。 -
入力に属性を追加します
name
~-
この追加された属性は、後続のデータ送信のキーと値のペアのキーになります ~
ユーザー名=張さん&パスワード=123
-
-
ボタン button を
input
ラベルに置き換えます~
<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="submut" value="提交">
</div>
</div>
</form>
</div>
実行を開始すると、送信ボタンのスタイルが機能しません
フロントエンド ページ開発のプロセスでは、HTML ページ構造は非常に重要です。その後の多くの CSS および JS はこのページ構造に依存します。ページ構造が調整されると、CSSまたは JS が失敗する可能性があります~~
ボタンIDを設定し、スタイルを調整し、調整後変化がなければctrl+f5
オリジナルのスタイル:
.row button {
width: 300px;
height: 50px;
border-radius: 10px;
color: white;
background-color: rgba(0, 128, 0);
border: none;
margin-top: 20px;
}
/* 鼠标按下未弹起 */
.row button:active {
background-color: #666;
}
調整:
.row #submit {
width: 300px;
height: 50px;
border-radius: 10px;
color: white;
background-color: rgba(0, 128, 0);
border: none;
margin-top: 20px;
}
/* 鼠标按下未弹起 */
.row #submit:active {
background-color: #666;
}
完全なコード:
<!-- 导航栏 -->
<div class="nav">
<img src="image/picture.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>
3. バックエンドコード LoginServlet
ここで合意されたパスは、/login
これが新しいパスであり、それを処理するには新しいサーブレットを使用する必要があるということです ~
LoginServletクラスの作成
username=zhangsan&password=123 リクエストのデータはこの形式であるため、次を使用する必要があります。
String username = req.getParameter("username");
を使用してgetParameter
パラメータを読み取る場合 パラメータの値が中国語の場合、この時点でパラメータを直接読み込むと文字化けが発生しやすくなります。
中国語にはフロントエンド ページの文字エンコーディングが含まれており、ブラウザには中国語の文字は utf8 であると通知されています。<meta charset="UTF-8">
したがって、ブラウザによって入力ボックスに入力された中国語の文字も utf8 を使用してエンコードされますが、サーブレットはデフォルトでは utf8 に従って解析しないため
、リクエストも utf8 に従って解析するようにサーブレットに指示する必要があります。
req.setCharacterEncoding("utf8");
これはリクエストに対して設定され、リクエストが utf8 形式を使用して解析されることを意味します。このエンコーディングがリクエストに追加されていない場合、この文字化けしたコードが表示され、データベースを正しくクエリできなくなります。
resp.setCharacterEncoding("utf8");
これは応答に設定されます。つまり、構築されたデータは utf8 に従って構築される必要があります。
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;
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf8");
resp.setCharacterEncoding("utf8");
// 1. 获取到请求中的参数
String username = req.getParameter("username");
String password = req.getParameter("password");
System.out.println("username=" + username + ", password=" + password);
if (username == null || "".equals(username) || password == null || "".equals(password)) {
resp.setContentType("text/html; charset=utf8"); //
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");
}
}
4.——検証
http://127.0.0.1:8080/blog_system/blog_login.html にアクセスしてください。
7. ログインステータスを確認する
ログイン機能が完了したら、前の2つのページ(ブログリストとブログの詳細)を、ログイン後にのみアクセスできるよう
に調整する必要があります~~
この制限はここで行われます(このように制限すると、後で他の機能を実装しやすくなります)
ブログ一覧ページ・詳細ページに入る際には、まずユーザーのログイン状況を確認し、ログイン中の場合はそのままご利用いただけます。
ログインしていない場合は、強制的にログインページにジャンプします。
上記の機能を実装する方法:
- 読み込み時にブログ一覧ページ/詳細ページから
ajax
サーバーにアクセスし、現在のログイン状況を取得し、取得できるか確認してください。 - 取得された場合は、現在ログインしていることを意味し、現時点ではこのページに留まることはできます。
- 取得できない場合はログインしていないことを意味するため、ログインページにジャンプする必要があります。
1. インタラクティブインターフェースに同意する
聞く:
GET /login
応答:
HTTP/1.1 200 OK
Content-Type:application/json;
{
userId: 1,
username: 'zhangsan',
}
ログインしている場合は、現在ログインしているユーザー情報が直接返されます。ログインしていない場合は、userld = 0 のオブジェクトが直接返されます (これは一般的な合意メソッドであり、他のメソッドも使用できます)ユーザーが現在ログインしていないことを示すために 403 を使用するなど、同意するために使用されます。ログイン…)
2. LoginServlet を変更する
doGet メソッドを追加します。
クラスに追加:
private ObjectMapper objectMapper = new ObjectMapper();
ユーザー属性を変更します。
private int userId = 0;
private String username = "";
private String password = "";
doGet メソッド:
これをサーバーから取得しsession
て内部に取得すると、user
ログインが成功したとみなされます!
(ログインが成功すると、サーバーは sessionld をクライアントに返し、ブラウザはこの sessionld を保存し、この ID を次のセッションに持ち出します) )
サーバーが sessionld を取得すると、ハッシュ テーブルでそれを確認し、現在のセッション オブジェクトが誰であるかを知ることができます~~
// 这个方法用来让前端检测当前的登录状态.
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("application/json; charset=utf8"); // 加载的时候,通过 ajax ,访问一下服务器,获取当前的登录状态
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. フロントエンドコード common.js を変更します。
common.jsを作成する
フロントエンドのページジャンプ方法:location.assign('login.html');
// 这个文件里放一些页面公共的代码
// 加上一个逻辑, 通过 GET /login 这个接口来获取下当前的登录状态
function getUserInfo() {
$.ajax({
type: 'get',
url: 'login',
success: function(body) {
// 判定此处的 body 是不是一个有效的 user 对象(userId 是否非 0)
if (body.userId && body.userId > 0) {
// 登录成功!
// 不做处理!
console.log("当前用户登录成功!用户名: " + body.username);
} else {
// 登录失败!
// 弹窗提示让用户 让前端页面, 跳转到 login.html
alert("当前您尚未登录! 请登录后再访问博客列表!");
location.assign('blog_login.html');
}
},
error: function() {
alert("当前您尚未登录! 请登录后再访问博客列表!");
location.assign('blog_login.html');
}
});
}
getUserInfo();
一覧ページと詳細ページで紹介:
<!-- 在这里引入上述的 js 文件, 就可以执行到里面的代码, 也就进行了登录状态的监测了 -->
<script src="js/common.js"></script>
4.——検証
サーバーを再起動し、再度リストページにアクセスすると、再度ウィンドウが表示されますので、クリックしてログインしてください。
ログインしたばかりですが、セッション情報はサーバーのメモリに保存されています(HttpSessionのハッシュテーブルはメモリ内にあります)サーバープロセスを再起動すると、当然メモリ上のデータは消えます~~ので、
毎回サーバーが再起動されました、もう一度ログインする必要があります~~