Servlet_博客系统_后端开发

通过 Servlet + JDBC +Cookie + Session + JSP + HTML 实现一个博客系统 后端开发

其实算不上是一个项目,就是细细碎碎将Servlet开发Web程序的所有点走了一遍,功能比较简单,但是很全面,基本上所有的点都覆盖了,整理到头秃的一篇博客,目的在于学会使用如何使用Servlet进行Web程序的开发

在这里插入图片描述

整体设计

1. 用户列表

1.1 功能列表

1)注册用户——信息来自form提交
2)用户登录——信息来自form提交
3)查看用户信息——username从url中获取
4)修改用户信息(前提是已经登录)—— 展示当前信息、修改后刷新的信息
5)通用功能:考虑基本的安全性

1.2. 库表设计

属性: id / username / password / nickname / brief / registered_at

CREATE DATABASE java20_blog04 charset utf8mb4;

USE java20_blog04;

CREATE TABLE users (
  id INT PRIMARY KEY AUTO_INCREMENT,
  username VARCHAR(100) NOT NULL UNIQUE,
  password CHAR(64) NOT NULL,
  nickname CHAR(64) NOT NULL,
  brief VARCHAR(200) NOT NULL,
  registered_at DATETIME NOT NULL
);
1.3 整理功能需要的 页面资源(API设计)

1)注册用户
GET / register,html 静态
POST/reg 动态
2)登录
GET / login.html 静态
POST / log 动态
3)获取用户信息
GET / u? username = … 动态
4)修改用户个人资料
GET / profile 动态
POST / profile / edit 动态

<一> 注册:

一、目录结构

在这里插入图片描述

二、静态页面
注册静态页面 register.html
!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>注册</title>
</head>
<body>
<form action="/reg" method="post">
    <div>
        用户名<input type="text" name="username" />
    </div>
    <div>
        昵称<input type="text" name="nickname" />
    </div>
    <div>
        密码<input type="password" name="password" />
    </div>
    <div>
        <button type="submit">注册</button>
    </div>

</form>
</body>
</html>
登录静态页面 login.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
<form action="/log" method="post">
    <div>
        用户名<input type="text" name="username" />
    </div>
    <div>
        密码<input type="password" name="password" />
    </div>
    <div>
        <button type="submit">登录</button>
    </div>

</form>
</body>
</html>
三、动态代码
注册 register.java
package com.peixinchen.servlets;

import com.peixinchen.model.User;

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 java.io.IOException;
import java.io.PrintWriter;
import java.sql.SQLException;

@WebServlet("/reg")
public class RegisterServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //首先,从请求中获取用户提交的信息(包括用户名、昵称、密码)

        req.setCharacterEncoding("utf-8"); //按照UTF-8编码形式,从 请求中 读取用户的提交内容

        String username = req.getParameter("username");
        String nickname = req.getParameter("nickname");
        String password = req.getParameter("password");


        //对用户输入的内容进行合法性校验——永远不要完全相信用户的输入
        if (username == null || username.isEmpty()){
            System.out.println("用户名不合法!");
            resp.sendRedirect("/register.html");//重新回到注册页面
            return;
        }
        if (nickname == null || nickname.isEmpty()){
            System.out.println("昵称不合法!");
            resp.sendRedirect("/register.html");//重新回到注册页面
            return;
        }
        if (password == null || password.isEmpty()){
            System.out.println("密码不合法!");
            resp.sendRedirect("/register.html");//重新回到注册页面
            return;
        }

        //进行用户的注册过程
        try {
            User user = User.register(username,nickname,password);
            System.out.println(user);
        }catch (SQLException e){
            e.printStackTrace();
            throw new ServletException(e);
        }

        //给出成功信息
        resp.setContentType("text/html");
        resp.setCharacterEncoding("utf-8");
        PrintWriter writer = resp.getWriter();
        writer.println("<h1>注册成功!</h1>");

    }
}
用户 User.java
package com.peixinchen.model;


import com.peixinchen.util.Database;

import java.sql.*;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

public class User {
    public int id;
    public String username;
    public String nickname;
    public String brief;
    public Date registered_at;

    public User(int id, String username, String nickname, String brief, Date registeredAt) {
        this.id = id;
        this.username = username;
        this.nickname = nickname;
        this.brief = brief;
        this.registered_at = registeredAt;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", nickname='" + nickname + '\'' +
                ", brief='" + brief + '\'' +
                ", registeredAt=" + registered_at +
                '}';
    }

    public static User register(String username, String nickname, String password) throws SQLException {
        int id;
        String brief = "这个人很懒,什么都没有留下~";
        Date registered_at = new Date();
        DateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");


        //SQL
        String sql = "INSERT INTO users (username,password,nickname,brief,registered_at) VALUES (?,?,?,?,?)";
        try (Connection con = Database.getConnection()){
            try (PreparedStatement stmt = con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)){
                //Statement.RETURN_GENERATED_KEYS 目的是获取插入后的自增id
                stmt.setString(1,username);
                stmt.setString(2,password);
                stmt.setString(3,nickname);
                stmt.setString(4,brief);
                stmt.setString(5,fmt.format(registered_at));

                stmt.executeUpdate();

                //插入成功后就可以通过 getGeneratedKeys() 获取自增id
                try (ResultSet rs = stmt.getGeneratedKeys()){
                    if (rs.next()){
                        id = rs.getInt(1);

                        return new User(id,username,nickname,brief,registered_at);
                    }
                }
            }
        }
        return null;

    }
}
四、运行结果

在这里插入图片描述
点击注册:
在这里插入图片描述
无视飘红哈,可以看到已经注册成功了
在这里插入图片描述
mysql也已经成功插入:
在这里插入图片描述

五、遇到的问题和解决办法
1、405错误

在这里插入图片描述
解决办法:
因为代码中用到的是POST,所以只支持POST,但是浏览器发来了GET,这里是不允许直接通过URL访问的
在这里插入图片描述
所以需要访问 register.html,登陆成功后会跳转到 reg
通过点击注册进行页面跳转

2、500错误

注册页面可以正常注册,注册完成后点击提交出现下图错误
在这里插入图片描述
从上面报错信息可以看出是sql的jar包导入有问题,最后得知是因为sql的jar包没有加入到artifacts里面:
在这里插入图片描述

<二> 对用户的密码进行哈希

(上面的方法可以看到对用户的密码是明文保存的,明显是不可取的,下面就是对密码进行哈希后存进数据库)

拓展:
清掉原有的表数据命令行:
在这里插入图片描述

一、哈希方法

在这里插入图片描述
在这里插入图片描述
User.java 整体代码:

package com.peixinchen.model;


import com.peixinchen.util.Database;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.*;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

public class User {
    public int id;
    public String username;
    public String nickname;
    public String brief;
    public Date registered_at;

    public User(int id, String username, String nickname, String brief, Date registeredAt) {
        this.id = id;
        this.username = username;
        this.nickname = nickname;
        this.brief = brief;
        this.registered_at = registeredAt;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", nickname='" + nickname + '\'' +
                ", brief='" + brief + '\'' +
                ", registeredAt=" + registered_at +
                '}';
    }

    public static User register(String username, String nickname, String password) throws SQLException {
        //把密码 Hash 后保存进数据库,防止因为数据库被拖库进而导致的用户密码泄露
        //现在常用的哈希方法:SHA256
        password = hash(password);


        int id;
        String brief = "这个人很懒,什么都没有留下~";
        Date registered_at = new Date();
        DateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");


        //SQL
        String sql = "INSERT INTO users (username,password,nickname,brief,registered_at) VALUES (?,?,?,?,?)";
        try (Connection con = Database.getConnection()){
            try (PreparedStatement stmt = con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)){
                //Statement.RETURN_GENERATED_KEYS 目的是获取插入后的自增id
                stmt.setString(1,username);
                stmt.setString(2,password);
                stmt.setString(3,nickname);
                stmt.setString(4,brief);
                stmt.setString(5,fmt.format(registered_at));

                stmt.executeUpdate();

                //插入成功后就可以通过 getGeneratedKeys() 获取自增id
                try (ResultSet rs = stmt.getGeneratedKeys()){
                    if (rs.next()){
                        id = rs.getInt(1);

                        return new User(id,username,nickname,brief,registered_at);
                    }
                }
            }
        }
        return null;

    }

    private static String hash(String password) {
        try {
            StringBuilder sb = new StringBuilder();
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            byte[] encrypted = digest.digest(password.getBytes("UTF-8"));

            for (byte b : encrypted) {
                sb.append(String.format("%02x",b));
            }
            return sb.toString();
        } catch (Exception e) {
            e.printStackTrace();
            return password;
        }
    }
}

再次进行注册,可以看到密码已经进行了哈希:
在这里插入图片描述

二、密码的安全处理

!! 如果将安全级别分为1~10分,我这里使用的安全级别仅仅为2分

数据库中保存的密码 = HASH(原始密码)
使用的 HASH 算法是 SHA-256 算法

而哈希是有碰撞的,可能不同的原始密码得到相同的哈希结果,因为有这种可能性。所以这个方法只能从左往右走,不能从右往左走,所以这就体现了安全性,即使知道了数据库中保存的密码,也不能得到真正的密码
在这里插入图片描述

<三> 查看用户信息

一、代码
查看用户信息 UserServlet.java
package com.peixinchen.servlets;

import com.peixinchen.model.User;

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 java.io.IOException;
import java.io.PrintWriter;
import java.sql.SQLException;
import java.text.ParseException;

@WebServlet("/u")
public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String username = req.getParameter("username");
        // TODO:增加合法性校验

        resp.setContentType("text/html;charset=utf-8");
        PrintWriter writer = resp.getWriter();
        writer.println("<!DOCTYPE html>");
        writer.println("<html lang=\"en\">");
        writer.println("<head>");
        writer.println("<meta charset='UTF-8'>");
        writer.println("<title>个人主页</title>");
        writer.println("</head>");
        writer.println("<body>");


        //根据用户名查看user信息
        User user = null;
        try {
            user = User.getByUsername(username);
        } catch (SQLException | ParseException e) {
            e.printStackTrace();
            throw new ServletException(e);
        }

        if (user == null){
            resp.setStatus(404);
            writer.println("<h1>没有这个用户: "+username+"</h1>");
        }else {
            writer.println("<h1>" + user.nickname + "</h1>");
            writer.println("<p>" + user.id + "</p>");
            writer.println("<p>" + user.username + "</p>");
            writer.println("<p>" + user.brief + "</p>");
            writer.println("<p>" + user.getRegisteredAt() + "</p>");
        }

        writer.println("</body>");
        writer.println("</html>");

    }
}
用户User.java:
package com.peixinchen.model;


import com.peixinchen.util.Database;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.*;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class User {
    private static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    public int id;
    public String username;
    public String nickname;
    public String brief;
    private Date registered_at;

    public String getRegisteredAt(){
        return dateFormat.format(registered_at);
    }

    public User(int id, String username, String nickname, String brief, Date registeredAt) {
        this.id = id;
        this.username = username;
        this.nickname = nickname;
        this.brief = brief;
        this.registered_at = registeredAt;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", nickname='" + nickname + '\'' +
                ", brief='" + brief + '\'' +
                ", registeredAt=" + registered_at +
                '}';
    }

    public static User register(String username, String nickname, String password) throws SQLException {
        //把密码 Hash 后保存进数据库,防止因为数据库被拖库进而导致的用户密码泄露
        //现在常用的哈希方法:SHA256
        password = hash(password);


        int id;
        String brief = "这个人很懒,什么都没有留下~";
        Date registered_at = new Date();


        //SQL
        String sql = "INSERT INTO users (username,password,nickname,brief,registered_at) VALUES (?,?,?,?,?)";
        try (Connection con = Database.getConnection()){
            try (PreparedStatement stmt = con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)){
                //Statement.RETURN_GENERATED_KEYS 目的是获取插入后的自增id
                stmt.setString(1,username);
                stmt.setString(2,password);
                stmt.setString(3,nickname);
                stmt.setString(4,brief);
                stmt.setString(5,dateFormat.format(registered_at));

                stmt.executeUpdate();

                //插入成功后就可以通过 getGeneratedKeys() 获取自增id
                try (ResultSet rs = stmt.getGeneratedKeys()){
                    if (rs.next()){
                        id = rs.getInt(1);

                        return new User(id,username,nickname,brief,registered_at);
                    }
                }
            }
        }
        return null;

    }


    //实际上也就是通过sql实现
    public static User getByUsername(String username) throws SQLException, ParseException {
        String sql = "SELECT id,nickname,brief,registered_at FROM users WHERE username = ?";

        User user = null;

        try (Connection con = Database.getConnection()){
            try (PreparedStatement stmt = con.prepareStatement(sql)){
                stmt.setString(1,username);
                try (ResultSet rs = stmt.executeQuery()){
                    if (rs.next()){
                        int id = rs.getInt(1);
                        String nickname = rs.getString(2);
                        String brief = rs.getString(3);
                        String registeredAtStr = rs.getString(4);
                        Date registered_at = dateFormat.parse(registeredAtStr);
                        user = new User(id,username,nickname,brief,registered_at);
                    }
                }
            }
        }
        return user;
    }

	//对密码进行哈希
    private static String hash(String password) {
        try {
            StringBuilder sb = new StringBuilder();
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            byte[] encrypted = digest.digest(password.getBytes("UTF-8"));

            for (byte b : encrypted) {
                sb.append(String.format("%02x",b));
            }
            return sb.toString();
        } catch (Exception e) {
            e.printStackTrace();
            return password;
        }
    }

}
二、运行结果

在这里插入图片描述

三 、代码分析
  1. 第一步从 querySting中拿到用户名,也就是url中
  2. 打印布局
  3. 从数据库获取用户信息
  4. 输出信息

<四> 查看用户信息的改进

如果用户没有传入用户名,则获取当前登录用户的信息

一、代码
查看用户信息 UserServlet.java
package com.peixinchen.servlets;

import com.peixinchen.model.User;

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;
import java.io.PrintWriter;
import java.sql.SQLException;
import java.text.ParseException;

@WebServlet("/u")
public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String username = req.getParameter("username");
        // TODO:增加合法性校验

        //如果没有传入用户名,则返回当前登录用户的信息
        //1.怎么获取当前用户(Cookie + Session)
        if (username == null){
            HttpSession session = req.getSession();
            User user = (User) session.getAttribute("user");
            if (user != null){//说明用户登录过了
                printHtmlUser(user,resp);
            } else {
                print404(resp,"必须传入用户名!");
            }
            return;
        }

        //根据用户名查看user信息
        User user;
        try {
            user = User.getByUsername(username);
        } catch (SQLException | ParseException e) {
            e.printStackTrace();
            throw new ServletException(e);
        }
        if (user == null){
            print404(resp,username);
        }else {
            printHtmlUser(user,resp);
        }

    }

    //打印用户主页
    private void printHtmlUser(User user,HttpServletResponse resp) throws IOException {
        resp.setContentType("text/html;charset=utf-8");
        PrintWriter writer = resp.getWriter();
        writer.println("<!DOCTYPE html>");
        writer.println("<html lang=\"en\">");
        writer.println("<head>");
        writer.println("<meta charset='UTF-8'>");
        writer.println("<title>个人主页</title>");
        writer.println("</head>");
        writer.println("<body>");
        writer.println("<h1>" + user.nickname + "</h1>");
        writer.println("<p>" + user.id + "</p>");
        writer.println("<p>" + user.username + "</p>");
        writer.println("<p>" + user.brief + "</p>");
        writer.println("<p>" + user.getRegisteredAt() + "</p>");
        writer.println("</body>");
        writer.println("</html>");
    }

    //打印404错误
    private void print404(HttpServletResponse resp,String username) throws IOException {
        resp.setStatus(404);
        resp.setContentType("text/html;charset=utf-8");
        PrintWriter writer = resp.getWriter();
        writer.println("<!DOCTYPE html>");
        writer.println("<html lang=\"en\">");
        writer.println("<head>");
        writer.println("<meta charset='UTF-8'>");
        writer.println("<title>个人主页</title>");
        writer.println("</head>");
        writer.println("<body>");
        writer.println("<h1>没有这个用户: "+username+"</h1>");
        writer.println("</body>");
        writer.println("</html>");
    }
}
二、运行结果

未登录情况下直接查看个人信息
在这里插入图片描述

<五>注册成功直接访问个人主页是没有结果的

到这里为止只是注册成功了,直接访问 /u 是没有结果的,为什么呢?

注册成功不等于登陆成功

在这里插入图片描述
可以看到获取用户是从Session中获取的,但是此时Session中是没有东西的

一、 将信息存入Session

所以要在注册时,注册成功后讲用户信息放入Session
在这里插入图片描述

二、 代码

RegisterServlet.java
package com.peixinchen.servlets;

import com.peixinchen.model.User;

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;
import java.io.PrintWriter;
import java.sql.SQLException;

@WebServlet("/reg")
public class RegisterServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //首先,从请求中获取用户提交的信息(包括用户名、昵称、密码)

        req.setCharacterEncoding("utf-8"); //按照UTF-8编码形式,从 请求中 读取用户的提交内容

        String username = req.getParameter("username");
        String nickname = req.getParameter("nickname");
        String password = req.getParameter("password");


        //对用户输入的内容进行合法性校验——永远不要完全相信用户的输入
        if (username == null || username.isEmpty()){
            System.out.println("用户名不合法!");
            resp.sendRedirect("/register.html");//重新回到注册页面
            return;
        }
        if (nickname == null || nickname.isEmpty()){
            System.out.println("昵称不合法!");
            resp.sendRedirect("/register.html");//重新回到注册页面
            return;
        }
        if (password == null || password.isEmpty()){
            System.out.println("密码不合法!");
            resp.sendRedirect("/register.html");//重新回到注册页面
            return;
        }

        resp.setContentType("text/html");
        resp.setCharacterEncoding("utf-8");
        PrintWriter writer = resp.getWriter();

        //进行用户的注册过程
        try {
            User user = User.register(username,nickname,password);
            System.out.println(user);

            //给出成功信息
            writer.println("<h1>注册成功!</h1>");

            //登录用户,也就是把注册成功的用户放入Session中
            HttpSession session = req.getSession();
            session.setAttribute("user",user);//key要和UserServlet对的上
        }catch (SQLException e){
            e.printStackTrace();
            throw new ServletException(e);
        }

    }
}

三、运行结果

注册:在这里插入图片描述
注册成功:
在这里插入图片描述
访问个人用户信息:
在这里插入图片描述

四、 过程分析:

在这里插入图片描述

<六> 修改用户资料

(由于到这里还没有写登录页面,所以只能靠注册后直接到修改用户资料页面来修改)

一、修改界面代码

修改个人信息 ProfileServlet.java
package com.peixinchen.servlets;


import com.peixinchen.model.User;

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;
import java.io.PrintWriter;

@WebServlet("/profile")
public class ProfileServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HttpSession session = req.getSession();
        User user =(User) session.getAttribute("user");

        if (user == null){//说明用户没有登录
            //跳转到用户登录页
            resp.sendRedirect("/login.html");
            return;
        }

        resp.setContentType("text/html;charset=utf-8");
        PrintWriter writer = resp.getWriter();
        writer.println("<!DOCTYPE html>");
        writer.println("<html>");
        writer.println("    <head>");
        writer.println("    <head>");
        writer.println("         <meta charset='UTF-8'>");
        writer.println("         <title>个人资料修改</title>");
        writer.println("    </head>");
        writer.println("    <body>");
        writer.println("        <form action='/profile/edit' method='post'>");
        writer.println("            <div>");
        writer.println("                昵称<input type='text' name='nickname' value='" + user.nickname+"'>");
        writer.println("            </div>");
        writer.println("            <div>");
        writer.println("                个性签名<input type='text' name='brief' value='" + user.brief+"'>");
        writer.println("            </div>");
        writer.println("            <div>");
        writer.println("                <button type='submit'>修改</button>");
        writer.println("            </div>");
        writer.println("        </form>");
        writer.println("    </body>");
        writer.println("</html>");
    }
}

二、运行结果

注册后访问profile就可以点击输入框进行修改:
在这里插入图片描述

三、引入JSP:

有一种场景:资源是动态的,但HTML内容特别多,纯粹在Servlet中写是可以实现的,由下图可见,是非常的麻烦,所以引入了JSP
在这里插入图片描述
JSP的本质就是Servlet,只是把拼HTML的过程交给Tomcat内部去处理了

profile.jsp
<%@ page import="com.peixinchen.model.User" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
    User user = (User) session.getAttribute("user");
    if (user == null){
        response.sendRedirect("/login.html");
        return;
    }

%>

<html>
<head>
    <meta charset="UTF-8">
    <title>个人资料修改</title>
</head>
<body>
<form action="/profile/edit" method="post">
    <div>
        昵称<input type="text" name="nickname" value="<%= user.nickname%>">
    </div>
    <div>
        个性签名<input type="text" name="brief" value="<%= user.brief%>">
    </div>
    <div>
        <button type="submit">修改</button>
    </div>
</form>
</body>
</html>
运行结果:

可以看到效果是一样的:
在这里插入图片描述

四、修改信息

修改的是谁的信息呢?
这个时候HTTP无状态的特性就体现出来了,导致我不知道修改的是谁的信息
所以需要通过登录状态处理
通过Session获取

ProfileEditServlet.java
package com.peixinchen.servlets;

import com.peixinchen.model.User;

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;
import java.sql.SQLException;

@WebServlet("/profile/edit")
public class ProfileEditServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf-8");
        String nickname = req.getParameter("nickname");
        String brief = req.getParameter("brief");
        
        //TODO:合法性校验

        HttpSession session = req.getSession();
        User user =(User) session.getAttribute("user");
        if (user == null){
            //用户没有登录
            resp.sendError(401);
            return;
        }
        //修改用户信息,修改的是哪个用户的资料?Session
        try {
            User.update(user.id,nickname,brief);
        } catch (SQLException e) {
            e.printStackTrace();
            throw new ServletException(e);
        }

		//修改后将Session中的也要修改
        user.nickname = nickname;
        user.brief = brief;
        session.setAttribute("user",user);
        
        resp.sendRedirect("/u");
    }
}
User.java _ update()
 //修改用户信息
    public static void update(int id, String nickname, String brief) throws SQLException {
        String sql = "UPDATE users SET nickname = ?, brief = ? WHERE id = ?";
        try(Connection con = Database.getConnection()) {
            try (PreparedStatement stmt = con.prepareStatement(sql)){
                stmt.setString(1,nickname);
                stmt.setString(2,brief);
                stmt.setInt(3,id);

                stmt.executeUpdate();
            }
        }
    }

<七> 用户登录

一、代码

LoginServlet.java
package com.peixinchen.servlets;

import com.peixinchen.model.User;

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;
import java.io.PrintWriter;
import java.sql.SQLException;

@WebServlet("/log")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String username = req.getParameter("username");
        String password = req.getParameter("password");

        //TODO:合法性校验

        try {
            User user = User.login(username,password);
            resp.setContentType("text/html;charset=utf-8");
            PrintWriter writer = resp.getWriter();
            if (user != null) {
                //登录成功
                HttpSession session = req.getSession();
                session.setAttribute("user", user);
                writer.println("<h1>登陆成功!</h1>");
            }else {
                //登陆失败
                writer.println("<h1>登陆失败!</h1>");
            }

        } catch (SQLException e){
            throw new ServletException(e);
        }
    }
}
User.java _ login()
public static User login(String username, String password) throws SQLException {
        String sql = "SELECT id, nickname, brief, registered_at FROM users WHERE username = ? AND PASSWORD = ?";
        try (Connection con = Database.getConnection()) {
            try (PreparedStatement stmt = con.prepareStatement(sql)) {
                stmt.setString(1, username);
                stmt.setString(2, hash(password));  // 密码在保存时已经被 Hash 过了,查找也得进行一次 Hash
                try (ResultSet rs = stmt.executeQuery()) {
                    if (rs.next()) {
                        int id = rs.getInt("id");
                        String nickname = rs.getString("nickname");
                        String brief = rs.getString("brief");
                        Date registeredAt = dateFormat.parse(rs.getString("registered_at"));
                        return new User(id, username, nickname, brief, registeredAt);
                    } else {
                        return null;
                    }
                } catch (ParseException e) {
                    e.printStackTrace();
                    return null;
                }
            }
        }
    }

二、运行结果

在这里插入图片描述
在这里插入图片描述

<八> 文章管理

一、库表设计

头图 + 标题 + 内容

CREATE TABLE articles(
    id INT PRIMARY KEY AUTO_INCREMENT,
    cover_image VARCHAR(200) NOT NULL,
    author_id INT NOT NULL,
    title VARCHAR(200) NOT NULL,
    body VARCHAR(600) NOT NULL,
    published_at DATETIME NOT NULL
);
1. 如何在mysql中保存图片

1)把图片数据保存到本地磁盘上,以文件形式保存
MySQL中只保存 1.本地的路径 2.图片的URL
2)把图片数据保存到MySQL的字段中,用BLOB形式

我这里用的是的第一种,保存到本地磁盘上,用本地的路径

二、 文章发表(暂无头图)

  1. 通过JSP完成——form表单,允许用户填写要发表的文章信息
    为什么不用静态的?因为我们要求要先登录后才有权限发表文章
  2. 通过Servlet实现——发表文章
writer.jsp
<%@ page import="com.peixinchen.model.User" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<%
    User user =(User) session.getAttribute("user");
    if (user == null){
        //用户没有登录
        response.getWriter().println("<h1>请先登录再进行操作!</h1>");
        return;
    }
%>

<html>
<head>
    <meta charset="UTF-8">
    <title>发表文章</title>
</head>
<body>
    <h1>发表文章</h1>
    <form action="/publish" method="post">
        <div>
            标题:<input type="text" name="title">
        </div>
        <div>
            正文:<textarea name="body"></textarea>
        </div>
        <button type="submit">发表</button>
    </form>
</body>
</html>
PublishServlet.java
package com.peixinchen.servlets;

import com.peixinchen.model.Article;
import com.peixinchen.model.User;

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;
import java.io.PrintWriter;
import java.sql.SQLException;

@WebServlet("/publish")
public class PublishServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf-8");
        String title = req.getParameter("title");
        String body = req.getParameter("body");

        HttpSession session = req.getSession();
        User user =(User) session.getAttribute("user");
        if (user == null){
            //说明没有登录,跳转到登录页面
            resp.sendRedirect("/login.html");
        }

        resp.setContentType("text/html;charset=utf-8");
        PrintWriter writer = resp.getWriter();
        try {
            //存到数据库里面
            Article article = Article.publish(user,title,body);
            if (article != null){
                writer.println("<h1>文章发表成功!</h1>");
            } else {
                writer.println("<h1>文章发表失败!</h1>");
            }
        } catch (SQLException e){
            throw new ServletException(e);
        }

    }
}
Article.java
package com.peixinchen.model;


import com.peixinchen.util.Database;

import java.sql.*;
import java.util.Date;

public class Article {
    public int id;
    public String title;
    public String body;
    public Date publishedAt;

    public Article(int id, String title, String body, Date publishedAt) {
        this.id = id;
        this.title = title;
        this.body = body;
        this.publishedAt = publishedAt;
    }

    public static Article publish(User user, String title, String body) throws SQLException {
        Date publishedAt = new Date();
        String sql = "INSERT INTO articles (author_id,cover_image,title,body,published_at) VALUES(?,'',?,?,?)";
        try (Connection con = Database.getConnection()){
            try (PreparedStatement stmt = con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)){
                stmt.setInt(1,user.id);
                stmt.setString(2,title);
                stmt.setString(3,body);
                stmt.setString(4,User.dateFormat.format(publishedAt));

                stmt.executeUpdate();
                try (ResultSet rs = stmt.getGeneratedKeys()){
                    if (!rs.next()){
                        return null;
                    }

                    int id = rs.getInt(1);

                    Article article = new Article(id,title,body,publishedAt);
                    return article;
                }
            }
        }
    }
}

运行结果

在这里插入图片描述
输入内容点击发表:
在这里插入图片描述
在数据库可以看到数据已经成功插入了:
这里我找了半天的bug发现是我的JDBC把published写成了pulished,哎一古气死了
在这里插入图片描述

<九> 文章管理

  1. form表单的 attribute 部分
  2. form表单的 input 部分
  3. Servlet 的 注解部分
  4. Servlet 的 处理部分

一、交换数据

writer.jsp
<%@ page import="com.peixinchen.model.User" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<%
    User user =(User) session.getAttribute("user");
    if (user == null){
        //用户没有登录
        response.getWriter().println("<h1>请先登录再进行操作!</h1>");
        return;
    }
%>

<html>
<head>
    <meta charset="UTF-8">
    <title>发表文章</title>
</head>
<body>
    <h1>发表文章</h1>
    <form action="/publish" method="post" enctype="multipart/form-data">
        <div>
            头图:<input type="file" name="cover_image">
        </div>
        <div>
            标题:<input type="text" name="title">
        </div>
        <div>
            正文:<textarea name="body"></textarea>
        </div>
        <button type="submit">发表</button>
    </form>
</body>
</html>
上传文件

在这里插入图片描述
当运行后上传文件,通过抓包工具就可以看到这里数据是已经被成功发送了
说明 form 中通过增加 enctype 和 input 中 type = “file” 已经可以将数据成功发送了,服务器肯定是能收到了,所以下一步要做的就是修改服务器端代码,去接收数据

读取文件

1. @MutiplePartConfig
加了注解之后就像平常读数据那样从请求中读取就ok了
在这里插入图片描述
2.加上注解后 通过Part去读

		//Part用法及相关属性
        Part coverImage = req.getPart("cover_image");
        System.out.println(coverImage.getName());
        System.out.println(coverImage.getSubmittedFileName());
        System.out.println(coverImage.getContentType());
        System.out.println(coverImage.getSize());
        InputStream inputStream = coverImage.getInputStream();
        Scanner scanner = new Scanner(inputStream,"UTF-8");
        System.out.println(scanner.next());

运行结果:
上传文件
在这里插入图片描述
点击提交 代码打印结果:

总结:

通过form表单上传文件时:

  • 客户端 —— 数据可以正确的被发送
    1) form表单增加 enctype 部分
    2) 添加 input type=file
  • 服务端 —— 正确的接收发送来的数据
    1)Servlet 类上增加 @MutiplePartConfig
    2) 代码内部通过 Part 对象读取相应的内容

二、上传文件功能演示

获取绝对路径方法:这个路径是可以自己选择的
在这里插入图片描述

代码:
package com.peixinchen.servlets;

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.*;

@WebServlet("/publish")
@MultipartConfig
public class PublishServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf-8");
        Part coverImage = req.getPart("cover_image");
        String filename = coverImage.getSubmittedFileName();
        String path = "F:\\IDEA\\Servlet-博客\\web\\images\\" + filename;

        InputStream is = coverImage.getInputStream();//文件来源

        //类似IO时,文件复制的代码,从输入不停的读,将他写到输出里
        byte[] buffer = new byte[8192];
        int len;
        try (OutputStream os = new FileOutputStream(path)) {//最终文件要放的位置
            while ((len = is.read(buffer)) != -1 ){
                os.write(buffer,0,len);
            }
        }
    }
}
运行结果

1、上传一个 .txt 文件
现在的images下是没有东西的:
在这里插入图片描述
我们开始上传文件:
文件:
在这里插入图片描述
点击发表:
在这里插入图片描述
可以看到文件成功上传到了images目录下:
在这里插入图片描述
在这里插入图片描述
2、上传一个图片
在这里插入图片描述

在浏览器访问刚刚上传的文件

注意: Tomcat访问的是编译后生成的out部分,所以要通过浏览器访问的话,需要把images完整复制到out下面,也可以在上传文件时就将路径选择在这个下面,上传之后就可以直接访问
在这里插入图片描述
访问:啊啊啊啊啊伯贤好帅!!对不起跑偏了
在这里插入图片描述

三、发表文章

图片保存到本地

  1. 图片保存下来后,还可以通过tomcat去访问,因为web下放的都是静态资源,可以通过tomcat去访问的,所以就将图片放到 web/images 下
  2. 保存图片的时候,使用什么文件名称保存
    1)使用原有得到文件名:
    缺点:如果有恶意用户,准备一个特殊的文件名,造成安全问题 ; 文件如果一样,就会被覆盖
    2)自己生成随机的、不重复的文件名
    现实中主要使用这种
  3. 不可以使用相对路径(理论上是可以的,但是需要特殊处理下)
    相对路径是相对进程启动时所在的路径而言的
    我们运行的所有Servlet代码,都是在Tomcat这个进程内部运行的
    相对路径,默认是相对Tomcat的启动路径:Tomcat / bin
发表文章 publishServlet.java
package com.peixinchen.servlets;

import com.peixinchen.model.Article;
import com.peixinchen.model.User;

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.*;
import java.sql.SQLException;
import java.util.Scanner;

@WebServlet("/publish")
@MultipartConfig
public class PublishServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf-8");
        Part coverImage = req.getPart("cover_image");
        String filename = coverImage.getSubmittedFileName();
		//下面有两种获取路径的方法
        String path = req.getServletContext().getRealPath("images") + "\\" + filename;
        //String path = "F:\\IDEA\\Servlet-博客\\out\\artifacts\\Servlet__Web_exploded\\images\\" + filename;
        InputStream is = coverImage.getInputStream();//文件来源

        //类似IO时,文件复制的代码,从输入不停的读,将他写到输出里
        byte[] buffer = new byte[8192];
        int len;
        try (OutputStream os = new FileOutputStream(path)) {//最终文件要放的位置
            while ((len = is.read(buffer)) != -1 ){
                os.write(buffer,0,len);
            }
        }


        req.setCharacterEncoding("utf-8");
        String title = req.getParameter("title");
        String body = req.getParameter("body");

        String coverImageUrl = "/images/" + filename;
        
        HttpSession session = req.getSession();
        User user =(User) session.getAttribute("user");
        if (user == null){
            //说明没有登录,跳转到登录页面
            resp.sendRedirect("/login.html");
        }

        resp.setContentType("text/html;charset=utf-8");
        PrintWriter writer = resp.getWriter();
        try {
            //存到数据库里面
            Article article = Article.publish(user,title,body,coverImageUrl);
            if (article != null){
                writer.println("<h1>文章发表成功!</h1>");
            } else {
                writer.println("<h1>文章发表失败!</h1>");
            }
        } catch (SQLException e){
            throw new ServletException(e);
        }
    }
}
Articles.java _ publish()
public static Article publish(User user, String title, String body , String coverImageUrl) throws SQLException {
        Date publishedAt = new Date();
        String sql = "INSERT INTO articles (author_id,cover_image,title,body,published_at) VALUES(?,?,?,?,?)";
        try (Connection con = Database.getConnection()){
            try (PreparedStatement stmt = con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)){
                stmt.setInt(1,user.id);
                stmt.setString(2,coverImageUrl);
                stmt.setString(3,title);
                stmt.setString(4,body);
                stmt.setString(5,User.dateFormat.format(publishedAt));

                stmt.executeUpdate();
                try (ResultSet rs = stmt.getGeneratedKeys()){
                    if (!rs.next()){
                        return null;
                    }

                    int id = rs.getInt(1);

                    return new Article(id,title,body,publishedAt);

                }
            }
        }
    }
运行结果

登录后,进行发表文章:在这里插入图片描述
在这里插入图片描述
查看数据库可以看到已经成功发布了:
在这里插入图片描述
可以看到头图cover_image的信息,将信息复制拿到浏览器访问,可以看到成功拿到了头图:
在这里插入图片描述

四 、 文章详情页

Article.java中成员加上:

public String coverImage;
a.jsp
<%@ page import="com.peixinchen.model.Article" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
    int id = Integer.parseInt(request.getParameter("id"));
    //TODO : 合法性校验

    Article article = Article.getById(id);
    if (article == null){
        response.setStatus(404);
        return;
        
    }
    
%>
<html>
<head>
    <meta charset="UTF-8">
    <title><%= article.title %>></title>
</head>
<body>
    <img src="<%= article.coverImage %>">
    <h1><%= article.title %></h1>
    <p><%= article.body %></p>
    <!-- TODO : 作者、文章发表时间 都可以写 这里没有写 -->
</body>
</html>
Article.java_getById()
  public static Article getById(int id) throws SQLException {
        String sql = "SELECT cover_image,title,body,published_at FROM articles WHERE id = ?";
        try (Connection con = Database.getConnection()){
            try (PreparedStatement stmt = con.prepareStatement(sql)) {
                stmt.setInt(1,id);
                try (ResultSet rs = stmt.executeQuery()){
                    if (!rs.next()) {
                        return null;
                    }

                    String coverImage = rs.getString(1);
                    String  title = rs.getString(2);
                    String body = rs.getString(3);
                    Date publishedAt = User.dateFormat.parse(rs.getString(4));

                    Article article = new Article(id,title,body,publishedAt);
                    article.coverImage = coverImage;//这其实就是构造方法,我没有改构造方法是因为,改了之后前面很多代码都无法运行了
                    return article;

                } catch (ParseException e){
                    return null;
                }
            }
        }
运行结果

在这里插入图片描述

在这里插入图片描述

五、文章列表

JSON格式返回

JSON的jar包

把jar包拷到lib目录下,依赖加进去
libraries加进去了之后别忘了Artifacts:
在这里插入图片描述

https://search.maven.org/
可以在官网下载想要的jar包
这个是我用的:在这里插入图片描述

本地方法实现
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;

public class Main {
    public static void main(String[] args) {
        JSONArray array = new JSONArray();

        //当成key-value去使用就好了
        JSONObject o1 = new JSONObject();
        o1.put("id",1);
        o1.put("cover_image","images/1.png");
        o1.put("title","title1");
        o1.put("body","body1");
        array.add(o1);//到这里就有一个对象了

        //同理,可以有很多个对象
        JSONObject o2 = new JSONObject();
        o2.put("id",2);
        o2.put("cover_image","images/2.png");
        o2.put("title","title2");
        o2.put("body","body2");
        array.add(o2);

        System.out.println(array.toJSONString());
    }
}

运行结果:
在这里插入图片描述

Servlet实现 ListServlet.java && Article.java_list()
package com.peixinchen.servlets;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.peixinchen.model.Article;

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 java.io.IOException;
import java.io.PrintWriter;
import java.sql.SQLException;
import java.util.List;

@WebServlet("/list.json")
public class ListServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("application/json;charset=utf-8");
        PrintWriter writer = resp.getWriter();

        try {
            List<Article> articleList = Article.list();

            String jsonString = transform(articleList);
            writer.println(jsonString);
        } catch (SQLException e){
            throw new ServletException(e);
        }
    }

    private String transform(List<Article> articleList) {
        JSONArray array = new JSONArray();
        for (Article article : articleList){
            JSONObject o = new JSONObject();
            o.put("id",article.id);
            o.put("cover_image",article.coverImage);
            o.put("title",article.title);
            o.put("body",article.body);

            array.add(o);
        }

        return array.toJSONString();
    }
}

Article.java_list()

public static List<Article> list() throws SQLException{
        String sql = "SELECT id,cover_image,title,body,published_at FROM articles ORDER BY id DESC";
        List<Article> articleList = new ArrayList<>();
        try (Connection con = Database.getConnection()){
            try (PreparedStatement stmt = con.prepareStatement(sql)) {
                try (ResultSet rs = stmt.executeQuery()){
                    while (rs.next()){
                        int id = rs.getInt(1);
                        String coverImage = rs.getString(2);
                        String title = rs.getString(3);
                        String body = rs.getString(4);
                        try {
                            Date publishedAt = User.dateFormat.parse(rs.getString(5));
                            Article article = new Article(id,title,body,publishedAt);
                            article.coverImage = coverImage;
                            articleList.add(article);

                        } catch (ParseException e){
                            continue;
                        }
                    }
                }
            }
        }
        return articleList;
    }
运行结果:

在这里插入图片描述

如何保证Servlet项目中的字符集正确

<十> 用户注销

删掉Cookie其实就相当于用户注销了

<十一> 项目总结:

在这里插入图片描述

<项目的几个注意点>

1. 快速删除掉数据库表中所有数据命令行:truncate users;

在这里插入图片描述

2. 读输入的时候一定要设置字符集编码,不然遇到中文的数据读过来就是乱码
3. 所有用到的API记得全部要放到Artifacts的lib

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值