ブログシステム Java Web開発(サーブレット)

目次

1. 準備作業

2. 設計データベース

3. データベースコードを書く

1.テーブルSQLを作成する

2. データベース接続操作をカプセル化する

3. エンティティクラスの作成

4. データベースの追加、削除、変更、クエリをカプセル化します。

(1)ブログダオ

新しいブログ: 

ブログ ID に基づいて指定されたブログをクエリします (ブログの詳細ページの場合)

データベース内のすべてのブログのリストを直接クエリします。

ブログを削除する

(2)ユーザーダオ

userIdに基づいてユーザー情報をクエリします

 ユーザー名に基づいてユーザー情報を照会します (ログイン時)

4. ブログ一覧ページ周辺にブログ一覧を取得する機能を実装

1. フロントエンドとバックエンドの対話インターフェイスについて合意する

2. バックエンドコードを書く

3. フロントエンドコードを書く

5. ブログ詳細ページ

1. フロントエンドとバックエンドの対話インターフェイスについて合意する

2. バックエンドコードを実装する

3. フロントエンドコードを実装する

6. ログインページの実装

1. フロントエンドとバックエンドの対話インターフェイスについて合意する

2. フロントエンドコードを変更する

3. バックエンドコードを変更する

7. ページへの強制ログイン

1. フロントエンドとバックエンドの対話インターフェイスについて合意する

2. バックエンドコードを書く

3. フロントエンドコードを実装する

8. ユーザー情報の表示

1. フロントエンドとバックエンドの対話インターフェイスについて合意する

 2. バックエンドコードを書く

3. フロントエンドコードを書く

9. ログイン状態からログアウトする

1. フロントエンドとバックエンドのインターフェースについて合意する

2. バックエンドコードを書く

3. フロントエンドコードを書く

10. ブログを公開する

1. フロントエンドとバックエンドの対話インターフェイスについて合意する

編集

2. サーバーコードを書く

3. クライアントコードを書く


1. 準備作業

プロジェクトを作成し、依存関係を導入し、前に作成したフロントエンド ページをコピーします。

導入される依存関係は次のとおりです: servlet、mysql、jackson

依存関係を導入した後、ディレクトリを作成し、web.xml の内容をコピーしてそこに貼り付けます。

<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
        <display-name>Archetype Created Web Application</display-name>
</web-app>

次に、前に作成したフロントエンド コードをコピーして webapp ディレクトリに貼り付けます。

 これで準備作業は完了です


2. 設計データベース

以前の要件と組み合わせると、現在のブログ システムには主にブログとユーザーという 2 つのエンティティが含まれます。

ブログとユーザーを表す 2 つのテーブルを作成できます

これら 2 つのエンティティの間にはどのような関係があるのでしょうか?

1 対多の関係。1 人のユーザーが複数のブログを作成できますが、1 つのブログは 1 人のユーザーにのみ属することができます。


3. データベースコードを書く

1.テーブルSQLを作成する

後で使用できるように、いくつかの基本的なデータベース操作をカプセル化します。

src ディレクトリに db.sql を作成し、その中にデータベースとデータベースのテーブル作成ステートメントを記述します。

    --这个文件,主要用来写建库建表语句
    --一般在建表的时候,把建表的 sql 保留下来,以备后续部署其它及其的时候就方便了

    create database if not exists blog_system;
    use blog_system;

    --删除旧表,重新创建新表,防止之前的残留数据面对后续的程序有负面影响
    drop table if exists user;
    drop table if exists blog;

    --进行建表
    create table blog(
        blogId int primary key auto_increment,
        title varchar(128),
        content varchar(4096),
        postTime datetime,
        userId int
    };

    create table user(
        userId int primary key auto_increment,
        username varchar(20) unique , --要求用户名和别人不同
        password varchar(20)
    );

2. データベース接続操作をカプセル化する

Java ディレクトリに DBUtil クラスを作成します。

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;

//通过这个类,把数据库连接过程封装一下
//此处,把 DBUtil 作为一个工具类,提供 static 方法,供其它方法来调用
public class DBUtil {
    //静态成员是跟随类对象的,类对象在整个进程中,只有唯一一份
    //静态成员相当于也是唯一的实例(单例模式,饿汉模式)
    private static DataSource dataSource = new MysqlDataSource();

    static {
        //使用静态代码块,针对 dataSourse 进行初始化
        ((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/java?charactorEncoding=utf8&useSSL=false");
        ((MysqlDataSource)dataSource).setUser("root");
        ((MysqlDataSource)dataSource).setPassword("123456");
    }

    //通过这个方法来建立连接
    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }

    //通过这个方法来断开连接,释放资源
    public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet)  {
        //此处的三个 try catch 分开写更好,避免前面的异常导致后面的代码无法执行
        if (resultSet != null){
            try {
                resultSet.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        if (statement != null){
            try {
                statement.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        if (connection != null){
            try {
                connection.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

3. エンティティクラスの作成

エンティティクラスはテーブル内のレコードに対応するクラスです

エンティティ クラスに必要な属性は、現在のテーブルの列と密接に関連しています。

したがって、前の表の列に基づいて、blog と user という 2 つのクラスを作成する必要があります。

さらに、内部の各プロパティに get set アクセサーを手動で追加します。


4. データベースの追加、削除、変更、クエリをカプセル化します。

ブログテーブル用の BlogDao を作成する

ユーザーテーブルの場合、UserDaoを作成します。

追加、削除、変更、クエリを実行するためのいくつかのメソッドが提供されます。

(1)ブログダオ

 合計 4 つの方法があります。

1. 新しいブログを追加する

2. ブログ ID に従って指定されたブログをクエリします (ブログの詳細ページの場合)

3. データベース内のすべてのブログのリストを直接クエリします。

4. ブログを削除する

新しいブログ: 

    //1、新增一个博客
    public void add(Blog blog){
        Connection connection = null;
        PreparedStatement statement = null;
        //1、和数据库建立连接
        try {
             connection = DBUtil.getConnection();
            //2、构造 Sql
            String sql = "insert into blog values(null,?,?,?,?)";
             statement = connection.prepareStatement(sql);
             statement.setString(1,blog.getTitle());
             statement.setString(2,blog.getContent());
             statement.setTimestamp(3,blog.getPostTime());
             statement.setInt(4,blog.getUserId());
            //3、执行 sql
            statement.executeUpdate();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }finally {
            DBUtil.close(connection,statement,null);
        }
    }

ブログ ID に基づいて指定されたブログをクエリします (ブログの詳細ページの場合)

    //2、根据博客 id 来查询指定博客(用于博客详情页)
    public Blog selectById(int blogId){
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try{
            //1、和数据库建立连接
            connection = DBUtil.getConnection();
            //2、构造 SQL 语句
            String  sql = "select *from blog where blogId = ?";
            statement = connection.prepareStatement(sql);
            statement.setInt(1,blogId);
            //3、执行 sql
            resultSet = statement.executeQuery();
            //4、遍历结果集合,由于此处的 blogId ,在 blog 表中是唯一的(主键)
            //此时的查询结果,要么是没有查到任何数据,要么是只有一条记录
            //所以,此处使用 if 即可,不用使用 while
            if (resultSet.next()){
                Blog blog = new Blog();
                blog.setBlogId(resultSet.getInt("blogId"));
                blog.setTitle(resultSet.getString("title"));
                blog.setContent(resultSet.getString("content"));
                blog.setPostTime(resultSet.getTimestamp("postTime"));
                blog.setUserId(resultSet.getInt("userId"));
                return blog;
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }finally {
            //5、释放必要的资源
            DBUtil.close(connection,statement,resultSet);
        }
        return null;
    }

データベース内のすべてのブログのリストを直接クエリします。

注: ここのブログ一覧ページに表示されるブログの内容は、簡単な概要 (記事内容の一部) のみです。

記事全体ではなく、記事全体がブログの詳細ページに表示される必要があります。

したがって、長いテキストを切り取ることができます

    //3、直接查询出数据库中所有的博客列表(用于博客列表页)
    public List<Blog> selectAll(){
        List<Blog> blogs = new ArrayList<>();
        
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try{
            //1、和服务器建立连接
            connection = DBUtil.getConnection();
            //2、构造 sql 语句
            String sql = "select *from blog";
            statement = connection.prepareStatement(sql);
            //3、执行 sql
            resultSet =  statement.executeQuery();
            //4、遍历结果集合
            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() >= 100){
                    content = content.substring(0,100) + "...";
                }
                blog.setContent(content);
                blog.setPostTime(resultSet.getTimestamp("postTime"));
                blog.setUserId(resultSet.getInt("userId"));
                blogs.add(blog);
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }finally {
            DBUtil.close(connection,statement,resultSet);
        }
        return  blogs;
    }

ブログを削除する

    //4、删除指定博客
    public void delete(int blogId){
        Connection connection = null;
        PreparedStatement statement = null;
        try{
            //1、和数据库建立连接
            connection = DBUtil.getConnection();
            //2、构造 sql 语句
            String sql = "delete from blog where blogId = ?";
            statement = connection.prepareStatement(sql);
            statement.setInt(1,blogId);
            //3、执行 sql
            statement.executeUpdate();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }finally {
            //进行关闭
            DBUtil.close(connection,statement,null);
        }
    }

(2)ユーザーダオ

合計 2 つの方法があります。

1. userIdに基づいてユーザー情報をクエリします。

2. ユーザー名に基づいてユーザー情報を照会します (ログイン時)

userIdに基づいてユーザー情報をクエリします

    //1、根据 userId 来查询用户信息
    public User selectById(int userId){
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            //1、和数据库建立连接
            connection = DBUtil.getConnection();
            //2、构造 sql
            String sql = "select *from user where userId = ?";
            statement = connection.prepareStatement(sql);
            statement.setInt(1,userId);
            //3、执行 sql
            resultSet = statement.executeQuery();
            //4、遍历结果集合
            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) {
            throw new RuntimeException(e);
        }finally {
            DBUtil.close(connection,statement,resultSet);
        }
        return null;
    }

 ユーザー名に基づいてユーザー情報を照会します (ログイン時)

    //2、根据 username 来查询用户信息(登录的时候)
    public User selectByUsername(String username){
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            //1、和数据库建立连接
            connection = DBUtil.getConnection();
            //2、构造 sql
            String sql = "select *from user where username = ?";
            statement = connection.prepareStatement(sql);
            statement.setString(1,username);
            //3、执行 sql
            resultSet = statement.executeQuery();
            //4、遍历结果集合
            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) {
            throw new RuntimeException(e);
        }finally {
            DBUtil.close(connection,statement,resultSet);
        }
        return null;
    }

これを記述することで、データベース操作がカプセル化されます。


4. ブログ一覧ページ周辺にブログ一覧を取得する機能を実装

次に、ブログ一覧ページに注目して、ブログ一覧を取得する機能を実装します。

現在、ブログ一覧ページのデータはすべて固定されており、明らかに非科学的です。

正しいアプローチは、データベースを通じてデータを読み取り、ページに表示することです。 

ここでは、フロントエンドとバックエンドの対話操作をオープンにする必要があります。

ブログ リスト ページは、読み込み時に ajax 経由でサーバーにリクエストを送信します。サーバーはデータベースを確認し、ブログ リスト データを取得してブラウザに返します。ブラウザはそのデータに基づいてページ コンテンツを構築します。

このような対話型プロセスは、「フロントエンドとバックエンドの分離」とも呼ばれます。

フロント エンドは特定のページではなくバック エンドからのデータのみを要求し、バック エンドはデータのみを返します。この設定の目的は、フロント エンドとバック エンドをさらに分離することです。

以上がブログ一覧ページ実装の基本的な考え方です

次に、次のものが必要です。

1. フロントエンドとバックエンドの対話インターフェイスについて合意する

2. バックエンドコードの開発

3. フロントエンドコードの開発

1. フロントエンドとバックエンドの対話インターフェイスについて合意する

ブログ一覧ページでは、ブログ一覧を取得する機能において、フロントエンドがどのようなリクエストを送信し、バックエンドがどのようなレスポンスを返すかを合意する必要があります。

フロントエンドとバックエンドのインターフェイスを決定した後、フロントエンドとバックエンドはそれぞれこの形式に従って開発されます。


2. バックエンドコードを書く

@WebServlet("/blog")
public class BlogServlet extends HttpServlet {
    private ObjectMapper objectMapper = new ObjectMapper();
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        BlogDao blogDao = new BlogDao();
        List<Blog> blogs =  blogDao.selectAll();
        //需要把 blogs 转成符合要求的 json 格式的字符串
        String respJson = objectMapper.writeValueAsString(blogs);
        resp.setContentType("application/json;charset=utf8");
        resp.getWriter().write(respJson);
    }
}

このとき、ここにクラスをたくさん書けば書くほど、見た目が汚くなってしまうことがわかります。

現時点では、それらを分類して対処できます

モデル:データを管理・操作する部分

このサーブレットを API に配置します。API はクラス/メソッドである必要がありますが、「ネットワーク インターフェイス」(HTTP リクエストを処理して HTTP レスポンスを返す) にすることもできます。


3. フロントエンドコードを書く

ブログ リスト ページでは、読み込みプロセス中に ajax がトリガーされ、サーバー内のデータにアクセスします。

次に、取得したデータをページに構築します

ページコンテンツを構築する際には、以前に記述したHTMLコードを参照して構築することができます。

 <script src="./js/jquery.mini.js"></script>
    <script>
        //在页面加载时 ,向服务器发起一个请求,获取博客列表数据
        function getBlogs(){
            $.ajax({
                type:'get',
                url:'blog',
                success:function(body){
                    //响应的 body 是一个 json 字符串,此处已经被 jquery 自动解析成 js 对象数组了
                    //直接 for 循环遍历即可
                    let containerRight = document.querySelector('.containner');
                    for(let blog of body){
                        //构造页面内容,就参考之前写好的 html 代码
                        //构造整个博客 div 
                        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');
                        datteDiv.className = 'date';
                        dateDiv.innerHTML = blog.postTime;
                        blogDiv.appendChild(dateDiv);
                        //构造博客摘要
                        let descDiv = document.createElement('div');
                        descDiv.className = 'desc';
                        desc.innerHTML = blog.content;
                        blogDiv.appendChild(descDiv);
                        //构造查看全文按钮
                        let a  = document.createElement('a');
                        a.innerHTML = '查看全文 &gt;&gt;';
                        //期望点击之后,可以跳转到博客详情页,为了让博客详情页知道点了哪个博客,把  blogId 传过去
                        a.href = 'blog_detail.html?blogId=' + blog.blogId;
                        blogDiv.appendChild(a);

                        //把 blogDiv 给加到父元素中
                        containerRight.appendChild(blogDiv);
                    }
                }
            });
        }
        //要记得调用
        getBlogs();
    </script>

これでブログ一覧ページ機能が完成します。

この時点で、テスト ケースを挿入すると、次の 2 つの問題が見つかります。

 1. タイムスタンプ: タイムスタンプは表示されませんが、フォーマットされた時刻は表示されます。

時刻の書式設定にはクラスを使用する必要がありますが、SimpleDateFormat を使用すると時刻の変換に役立ちます。 

2. 順序としては、新しいブログが一番上、古いブログが一番下になります。

SQLに直接order byを追加するだけです


5. ブログ詳細ページ

次に、ブログの詳細ページを実装します。

「全文表示」ボタンをクリックするとブログ詳細ページへジャンプします

過去にジャンプした後、ブログの詳細ページで ajax を起動し、現在のブログの特定のコンテンツをサーバーから取得して表示します。

1. フロントエンドとバックエンドの対話インターフェイスについて合意する

注: ブログの詳細はここで取得されるため、ここのコンテンツは完全であり、ブログ リスト ページのように切り詰める必要はありません。 

2. バックエンドコードを実装する

@WebServlet("/blog")
public class BlogServlet extends HttpServlet {
    private ObjectMapper objectMapper = new ObjectMapper();
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //尝试获取一下 query string 中的 blogId 字段
        String blogId = req.getParameter("blogId");
        BlogDao blogDao = new BlogDao();
        if (blogId == null){
            //queryString 不存在,该请求是获取博客列表页
            List<Blog> blogs =  blogDao.selectAll();
            //需要把 blogs 转成符合要求的 json 格式的字符串
            String respJson = objectMapper.writeValueAsString(blogs);
            resp.setContentType("application/json;charset=utf8");
            resp.getWriter().write(respJson);
        }else {
            //query string 存在,本次请求是获取指定 id 的博客
            Blog blog = blogDao.selectById(Integer.parseInt(blogId));
            if (blog == null){
                System.out.println("当前 bligId = " + blogId + "对应博客不存在!");
            }
            String respJson = objectMapper.writeValueAsString(blog);
            resp.setContentType("application/json;charset=utf8");
            resp.getWriter().write(respJson);
        }
    }

3. フロントエンドコードを実装する

blog_detail.html に、上記のデータを取得するための ajax を追加します

location.search は、現在のページのクエリ文字列を取得します。

ここでの場所は http ヘッダー内の場所ではなく、ドキュメントと同様の JS コード内のグローバル オブジェクトです。

 注: 現在のブログ コンテンツはマークダウン形式で編成されています。たとえば、ブログ コンテンツは次のようになります。

コンテンツにはいくつかのマークダウン シンボルが含まれており、最終的には Web ページに表示されます。レンダリング後の結果、つまり # が第 1 レベルのタイトルに変換されることをユーザーが確認できることを期待しています。

データベースに保存されているコンテンツはレンダリング前のコンテンツですが、最終的にユーザーに表示されるのはレンダリング後のコンテンツです。

ここでは、editor.md を使用してマークダウン コンテンツを変換する必要があります

具体的にどうやって変換するのでしょうか?

editor.md は、editormd.markdownToHTML メソッドを提供します。その効果は、md 文字列を HTML フラグメントに変換し、それを #content タグに出力することです (使用するときは、editor.md の依存関係を導入することを忘れないでください)。

コードを変更した後、サーバーを再起動すると、ブログの詳細ページの結果が以前と同じであることがわかります。

このコンテンツは以前にハードコーディングされたコンテンツのままですが、実際にはコードによってこのコンテンツは削除されています。

これは、ブラウザ自体もキャッシュを持っているためで、ブラウザはサーバーからページを取得するのですが、この動作はネットワーク通信を介して完了するため、速度が比較的遅いです。

ブラウザーはページのコンテンツをローカル (クライアント コンピューターのハード ドライブ上) にキャッシュし、同じページに直接アクセスしてキャッシュを直接読み取ります。  

これにより問題が発生します。サーバー コードが変更された場合、クライアントはキャッシュにヒットした後、時間内に変更を検出できない可能性があります。

解決策: 強制更新、ctrl + f5。この時点では、ローカル キャッシュは無視され、サーバーが 100% 再アクセスされます。

キャッシュの問題が解決された後、現在のページが変更されたにもかかわらず、結果が依然として理想的ではないことがわかりました。エラー メッセージを確認すると、editormd のコードが正しく書かれていないことがわかります。変更を加えて、ページを再度更新してください。

修正してみると、本文はあるのですが、タイトルがありませんでした。

パケットをキャプチャしてサーバーから返された結果が期待どおりであるかどうかを確認することで、それがフロントエンドの問題なのかバックエンドの問題なのかを判断できます。

応答結果からバックエンドのコードに問題はないと判断でき、応答結果は正しいので、次にフロントエンドのコードを確認します。 

これは、フロントエンド コードに 2 つのタイトルがあるためであることがわかりますが、querySelector はデフォルトで最初のタイトルを返すため、タイトルが間違った場所で変更されてしまいます。

解決策: セレクターをより正確に記述するだけです

サーバーを再起動し、ページを再度更新すると、タイトルが正しく表示されることがわかります。

        <!-- 右侧信息 -->
        <div class="containner-right">
            <!-- 博客标题 -->
            <h3 class="title"></h3>
            <!-- 博客发布时间 -->
            <div class="date"></div>
            <!-- 博客正文  为了配合 editormd 进行格式转换,此处一定要改成 id -->
            <div id="content">

            </div>
        </div>
    </div>
    <script src="js/jquery.mini.js"></script>
        <!-- 引入 editor.md 的依赖 -->
        <!-- 要保证这个几个 js 的加载,在 jquery 之后,因为 edit.md 依赖了 jquery -->
        <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>
    <script>
        $.ajax({
            type:'get',
            url:'blog' + location.search,
            success:function(body){
                //处理响应结果,此处的 body 就是表示一个博客的 js 对象
                //1、更新标题
                let titleDiv = document.querySelector('.containner-right .title');
                titleDiv.innerHTML = body.title;
                //2、更新日期
                let dateDiv = document.querySelector('.date');
                dateDiv.innerHTML = body.postTime;
                //3、更新博客正文
                //此处,不应该直接把博客正文填充到这个标签里
                editormd.markdownToHTML('content',{markdown: body.content});

            }
        });
    </script>

6. ログインページの実装

ここにユーザー名とパスワードを入力し、「ログイン」をクリックすると、HTTP リクエストがトリガーされます。

サーバーはユーザー名とパスワードを検証し、その結果に基づいてログインが成功したかどうかを判定し、ログインに成功した場合はブログ一覧ページにジャンプします。

現状では単なる入力ボックスであり、リクエストを送信することができないため、フォームに変更する必要があります。

1. フロントエンドとバックエンドの対話インターフェイスについて合意する

2. フロントエンドコードを変更する

ページにフォームを追加して、ログイン操作をクリックするとリクエストをトリガーできるようにします。

1. form タグを追加し、その中で入力をラップします。

2. 入力プラス名属性

3. ボタンを変更して送信タイプを入力します

        <!-- 垂直水平居中的对话框 -->
        <div class="login-dialog">
            <form action="login" method="post">
                <h3>登录</h3>
                <div class="row">
                    <span>用户名</span>
                    <input type="text" id = "username" placeholder="手机号 / 邮箱" 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="登录"></button>
                </div>
            </form>
        </div>

3. バックエンドコードを変更する

ここで、ログインリクエストを処理するためのサーブレットを追加する必要があります。

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //设置请求的编码,告诉 servlet 按照什么格式来理解请求
        req.setCharacterEncoding("utf8");
        //设置响应的编码,告诉 servlet 按照什么格式来构造请求
        //resp.setCharacterEncoding("utf8");
        resp.setContentType("text/html;charset=utf8");
        //1、读取参数中的用户名和密码
        //注意!!如果用户名密码包含中文,此处的读取可能会乱码,所以设置一下 utf8
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        if (username == null || "".equals(username) || password == null || "".equals(password)){
            //登录失败
            String html = "<h3> 登陆失败! 缺少 username 或者 password 字段! </h3>";
            resp.getWriter().write(html);
            return;
        }
        //2、读数据库,看看用户名是否存在并且密码是否匹配
        UserDao userDao = new UserDao();
        User user = userDao.selectByUsername(username);
        if (user ==null){
            //用户不存在
            String html = "<h3> 登陆失败! 用户名 或 密码 错误!! </h3>";
            resp.getWriter().write(html);
            return;
        }
        if (!password.equals(user.getPassword())){
            //密码不对
            String html = "<h3> 登陆失败! 用户名 或 密码 错误!! </h3>";
            resp.getWriter().write(html);
            return;
        }
        //3、如果用户名和密码验证通过,登录成功,接下来创建会话,使用该会话来保存用户信息
        HttpSession session = req.getSession(true);
        session.setAttribute("user",user);
        //4、进行重定向,跳转到博客列表页
        resp.sendRedirect("blog_list.html");
    }
}

7. ページへの強制ログイン

ブログ一覧ページ/詳細ページ/編集ページにアクセスする際には、ログインする必要があります。

ユーザーがまだログインしていない場合は、ユーザーを強制的にログイン ページにジャンプさせます。

本質は必須であり、システムを使用するには、まずログインする必要があります。

実装のアイデア:

ページの読み込み時に、新しい ajax が特別に開始されます (1 ページで N 個の ajax を開始できます)

ブログ リスト ページを例に挙げます。

最初にブログリストを取得するリクエストを送信し、次に ajax を送信してユーザーのログイン状態を取得します。ユーザーがすでにログインしていれば問題ありませんが、そうでない場合はログインページにジャンプします。

1. フロントエンドとバックエンドの対話インターフェイスについて合意する


2. バックエンドコードを書く

    @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 user = new User();
            String respJson = objectMapper.writeValueAsString(user);
            resp.getWriter().write(respJson);
            return;
        }
        User user = (User) session.getAttribute("user");
        if (user == null){
            user = new User();
            String respJson = objectMapper.writeValueAsString(user);
            resp.getWriter().write(respJson);
            return;
        }
        //确实成功去除了 user 对象,直接返回即可
        String respJson = objectMapper.writeValueAsString(user);
        resp.getWriter().write(respJson);
    }

3. フロントエンドコードを実装する

                function checkLogin(){
                    $.ajax({
                        type:'get',
                        url:'login',
                        success:function(body){
                            if(body.userId && body.userId > 0){
                                //登录成功
                                console.log("当前用户已经登录")
                            }else{
                                //当前未登录
                                //强制跳转到登录页
                                location.assign('login.html');
                            }
                        }
                    });
                }

                checkLogin();

8. ユーザー情報の表示

このユーザー情報は現在ハードコーディングされていますが、動的にユーザー情報を生成できるようにしたいと考えています。

1. ブログ一覧ページの場合、ログインしているユーザー情報がここに表示されます。

2. ブログの詳細ページの場合、記事の作成者はこの時点で実際に存在します。

1. フロントエンドとバックエンドの対話インターフェイスについて合意する

ブログリストページのコード調整:

ブログ詳細ページのコードはサーブレットを書き換えます 

新しいサーブレットを作成する必要があるのはどのような場合ですか?

主に、新規か既存かに関係なく、現在のリクエストのパスを表示します。


 2. バックエンドコードを書く

@WebServlet("/author")
public class authorServlet extends HttpServlet {
    ObjectMapper objectMapper = new ObjectMapper();
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String blogId = req.getParameter("blogId");
        if (blogId == null){
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write("参数非法,缺少 blogId ");
            return;
        }
        //根据 blogId 查询 Blog 对象
        BlogDao blogDao = new BlogDao();
        Blog blog = blogDao.selectById(Integer.parseInt(blogId));
        if (blog == null){
            //博客不存在
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write("没有找到指定博客:blogId = " + blogId);
            return;
        }
        //根据 blog 中的 userId 找到对应的用户信息
        UserDao userDao = new UserDao();
        User author = userDao.selectById(blog.getUserId());
        String respJson = objectMapper.writeValueAsString(author);
        resp.setContentType("application/json;charset=utf8");
        resp.getWriter().write(respJson);
    }
}

3. フロントエンドコードを書く

        function getAuthor(){
            $.ajax({
                type:'get',
                url:'author' + location.search,
                success: function(body){
                    //把 username 给设置到页面上
                    let h3 = document.querySelector('.containner-left .card h3');
                    h3.innerHTML = body.username;
                }
            });
        }

        getAuthor();

9. ログイン状態からログアウトする

ここでのログアウトとは、アカウントを削除するのではなく、ログアウトすることを指します。

ログインステータスを確認します。

1. http セッション オブジェクトが見つかるかどうかを確認します。

2. セッションオブジェクトにユーザーオブジェクトがあるかどうかを確認します。

ログアウトするには、http セッションを強制終了するか、ユーザーを強制終了します。どちらかを選択してください。

セッションは存在するがユーザー オブジェクトがない場合も、ログインしていないと見なされます。

httpセッションを強制終了する場合は面倒なので、ユーザーオブジェクトを強制終了することにします。


1. フロントエンドとバックエンドのインターフェースについて合意する


2. バックエンドコードを書く

@WebServlet("/logout")
public class logoutServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HttpSession httpSession = req.getSession(false);
        if (httpSession == null){
            //未登录状态
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write("当前未登录!");
            return;
        }
        httpSession.removeAttribute("user");
        resp.sendRedirect("login.html");
    }
}

3. フロントエンドコードを書く


10. ブログを公開する

1. フロントエンドとバックエンドの対話インターフェイスについて合意する

フォームを使用するには、ページ上に複数のフォーム タグが必要であり、同時にフォームがブログのコンテンツを認識できる必要があります。


2. サーバーコードを書く

@Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //发布博客
        //读取请求,构造出 blog 对象,插入数据库
        HttpSession httpSession = req.getSession(false);
        if (httpSession == null){
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write("当前未登录,无法发布博客");
            return;
        }
        User user = (User) httpSession.getAttribute("user");
        if (user == null){
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write("当前未登录,无法发布博客");
            return;
        }
        //确保登录之后,就可以把作者给拿到了

        //获取博客标题和正文
        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 对象
        Blog blog = new Blog();
        blog.setTitle(title);
        blog.setContent(content);
        blog.setUserId(user.getUserId());
        //发布时间
        blog.setPostTime(new Timestamp(System.currentTimeMillis()));
        //插入数据
        BlogDao blogDao = new BlogDao();
        blogDao.add(blog);
        
        //跳转到博客列表页
        resp.sendRedirect("blog_list.html");
    }

3. クライアントコードを書く

コードを調整します。

1.フォームタグを追加する

2.inputタグにname属性を追加する

3. ボタンを入力ラベルに変更し、入力して送信します

4. Editor.md ドキュメントにはメソッドの記述が必要です: add textarea

editor.md はフォーム フォームもサポートしています。つまり、フォームに非表示のテキストエリアを置くことができ、editor.md はユーザーがテキストエリアに入力したマークダウン コンテンツを自動的に入力し、[送信] をクリックして自動的に送信します。

 この時点で、コードを再度実行して結果を確認してみましょう

このとき、私たちは問題を発見しました。コードが変更された後、エディターに遺伝子変異が生じました。

フロントエンドのコード スタイルに問題があります。Chrome 開発者ツールを観察することによってのみ問題を見つけることができます。

現在のフォームには問題があることがわかります

 具体的な分析は次のとおりです。

解決策: フォームの高さを設定する

この時点では、ブログを正常に公開できますが、現時点では文字化けの問題が発生しています。

文字化けが発生する状況は 2 つあります。

1.ブログ投稿時の文字化けについて

2. ブログ取得時の文字化けについて

どのような状況が当てはまるかを知るためにデータベースを見るだけで済みます。 

この時点で、送信時にコードが文字化けしていることがわかります。

注: タイトルと本文を取得するときは、リクエストされた文字セットを指定する必要があります。これは、リクエストの解析にどの文字セットを使用するかをサーブレットに指示することを意味します。

この時点で、もう一度 [送信] をクリックして、ブログを通常どおり公開します。

おすすめ

転載: blog.csdn.net/weixin_73616913/article/details/132514383