Servlet实践--留言板-v1

Posted Aristole

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Servlet实践--留言板-v1相关的知识,希望对你有一定的参考价值。

功能介绍:

  由三个jsp页面组成,在doGet中根据请求URL中的请求参数不同,跳转到不同的页面:

    页面1:显示整个留言板列表

    页面2:创建留言页面(包括用户、主题、内容和上传文件)

    页面3:在查看单个留言的详细内容(包括提供下载附件)

  在doPost中处理创建留言的逻辑

 

如何实现这些功能:

  1.使用什么来保存用户创建的留言(数据存储):

     使用一个Ticket类对象来保存用户创建的留言,包括用户名、评论主题、评论内容和附件。附件是用一个Attachment类的实例来表示,该类中包含附件名和附件内容的二进制表示。

     当需要查看某个留言详细信息的时候,可以通过一个存储在内存中的哈希map(ticketDatabase)(以一个唯一标识某个留言的id为键,以Ticket类对象为值)来找到该Ticket类对象,通过该对象就可以得到详细信息。

     当需要浏览整个留言列表时,可以通过遍历该ticketDatabase得到整个留言列表。

     当需要下载某个留言中的附件时,可以先通过该ticketDatabase找到该Ticket类对象,而在Ticket类对象中也同样保存着一个哈希map(attachments)(以附件名为键,以Attachment类对象为值),通过该哈希map就可以找到某个特定的附件。

  2.如何让servlet处理不同的请求(逻辑处理):

     根据请求URL中的请求参数不同,让处理请求的doGet()和doPost()方法调用不同功能的方法实现。例如:

        当不带任何请求参数的请求URL,默认其行为为浏览整个留言列表(action=list);

        当携带请求参数时,若action=create并以Get方式提交请求,则执行将请求和响应转发到ticketForm.jsp页面;

                 若action=view并以Get方式提交请求,则先从请求参数中获得ticketId(唯一标识某个特定的Ticket对象),然后通过ticketDatabase获得该ticketId对应的Ticket对象,把该ticketId和Ticket对象保存在请求中,然后转发到viewTicket.jsp页面

        当携带请求参数时,若action=download并以Get方式提交请求,则执行下载附件的相关操作:

            +先获得该ticketId和Ticket对象,然后通过请求参数中的attachment获得附件名,得到Ticket对象和附件名,就可以得到Attachment对象

            +通过Attachment对象,就可以得到附件内容的二进制表示(一个二进制数组)

            +强制浏览器询问用户是保存还是下载文件,设置附件的内容类型是通用的二进制内容类型

            +将附件内容的二进制数组写入ServletOutputStream输出流中

       当携带请求参数时,若action=list并以Get方式提交请求时,将ticketDatabase保存在请求中,然后转发到listTickets.jsp页面;

       当携带请求参数时,若action=createt并以Post方式提交请求时,将从表单中获取Ticket对象的相关属性(用户名、留言主题、留言内容、上传的文件),通过setXXX()方法分别设置Ticket对象的属性。其中文件上传时,需要将该文件转换中Part对象(filePart),Part对象可以表示一个上传的文件或者表单数据。然后生成一个唯一的ticketId,将附件内容通过IO读取的方式保存在Attachment对象中的二进制数组中,最后将ticketId和Ticket对象组成键值对添加到ticketDatabase中,把页面重定向到浏览单个留言详细内容的页面中

  3.显示:

    jsp页面处理显示操作

 

部署文件web.xml:

<jsp-config>
        <!-- jsp组属性,不同的jsp组可以设置不同的属性,若不同属性组发送匹配冲突时,遵循匹配精确优先原则 -->
        <jsp-property-group>
            <!-- 该jsp组属性将应用于哪些文件,在这里它将匹配在Web应用程序中所有以jsp和jspf文件结尾的文件-->
            <url-pattern>*.jsp</url-pattern>
            <url-pattern>*.jspf</url-pattern>
            <!-- jsp页面编码,它和page指令中的pagaEncoding特性一致-->
            <page-encoding>UTF-8</page-encoding>
            <!-- 允许使用jsp中的java,若为true,则禁止在jsp中使用java -->
            <scripting-invalid>false</scripting-invalid>
            <!-- 告诉web容器在所有属于该属性组的jsp的头部添加文件/WEB-INF/jsp/base.jspf -->
            <include-prelude>/WEB-INF/jsp/base.jspf</include-prelude>
            <!-- 告诉jsp转换器删除响应输出中的空白,只保留由指令、声明、脚本和其他JSP标签创建的文本,即可以产生干净的代码 -->
            <trim-directive-whitespaces>true</trim-directive-whitespaces>
            <!-- 默认的内容类型是text/html -->
            <default-content-type>text/html</default-content-type>
        </jsp-property-group>
    </jsp-config>

base.jspf:

<%@ page contentType="text/html; charset=utf-8" language="java"%>
<%@ page import="cn.example.Ticket, cn.example.Attachment" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

listTickets.jsp:

<%@ page session="false" import="java.util.Map" %>
<%
    @SuppressWarnings("unchecked")
    Map<Integer,Ticket> ticketDatabase = (Map<Integer, Ticket>)request.getAttribute("ticketDatabase");
%>

<!DOCTYPE html>
<html>
    <head>
        <title>留言板</title>
    </head>
    <body>
        <h2>留言板</h2>
        <a href="
            <c:url value="/tickets">
                <c:param name="action" value="create"/>            
            </c:url>
        ">创建留言</a><br/><br/>
        <%
            if(ticketDatabase.size() == 0){
                %><i>留言板中没有留言。</i><%
            }
            else{
                for(int id : ticketDatabase.keySet()){
                    String idString = Integer.toString(id);
                    Ticket ticket = ticketDatabase.get(id);
                    %>留言 #<%= idString %> : <a href="
                        <c:url value="/tickets">
                            <c:param name="action" value="view"/>
                            <c:param name="ticketId" value="<%= idString %>"/>
                        </c:url>
                    "><%=ticket.getSubject() %></a>(用户: <%= ticket.getCustomerName() %>) <br/>
                     <%
                }
            }
        %>
    </body>
</html>

ticketForm.jsp:

package cn.example;
/*
 * 一个简单的POJO,表示着一个附件类
 */
public class Attachment {
    private String name;        // 附件名
    private byte[] contents;    // 附件的内容以字节数组的形式保存
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public byte[] getContents() {
        return contents;
    }
    public void setContents(byte[] contents) {
        this.contents = contents;
    }
}

 

viewTicket.jsp:

<%
    String ticketId = (String) request.getAttribute("ticketId");
    Ticket ticket = (Ticket) request.getAttribute("ticket");
%>
<!DOCTYPE html>
<html>
    <head>
        <title>留言版</title>
    </head>
    <body>
        <h2>留言 #<%=ticketId %>: <%= ticket.getSubject() %></h2>
        <i>用户 - <%=ticket.getCustomerName() %></i> <br/><br/>
        <i>内容:</i><br/>
        <%= ticket.getBody() %> <br/><br/>
        <%
            if(ticket.getNumberOfAttachments() > 0){
            %>附件:<%
                int i = 0;
                for(Attachment a:ticket.getAttachments()){
                    if(i++ > 0)
                        out.print(", ");
                    %>
                    <a href="
                        <c:url value="/tickets">
                            <c:param name="action" value="download"/>\\
                            <c:param name="ticketId" value="<%= ticketId %>"/>
                            <c:param name="attachment" value="<%=a.getName() %>"/>
                        </c:url>
                        "><%=a.getName() %> </a><%
                }    
            }
        %><br/>
        <a href="<c:url value="/tickets"/>">返回留言板主页</a>
    </body>
</html>

Attachment.java

package cn.example;
/*
 * 一个简单的POJO,表示着一个附件类
 */
public class Attachment {
    private String name;        // 附件名
    private byte[] contents;    // 附件的内容以字节数组的形式保存
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public byte[] getContents() {
        return contents;
    }
    public void setContents(byte[] contents) {
        this.contents = contents;
    }
}

 

Ticket.java:

package cn.example;
/*
 * 一个简单的POJO,表示一个票据类
 */

import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;

public class Ticket {
    private String customerName; // 用户名
    private String subject; // 评论内容的主题
    private String body;           // 评论内容的主体
    // 使用哈希map表示附件数据库,以附件名为键,以附件为值
    private Map<String, Attachment> attachments = new LinkedHashMap<String, Attachment>();
    
    public String getCustomerName() {
        return customerName;
    }
    
    public void setCustomerName(String customerName) {
        this.customerName = customerName;
    }
    
    public String getBody() {
        return body;
    }
    
    public void setBody(String body) {
        this.body = body;
    }
    
    public Attachment getAttachment(String name){
        return this.attachments.get(name);
    }
    
    public Collection<Attachment> getAttachments() {
        return this.attachments.values();
    }
    
    public void addAttachments(Attachment attachment) {
        this.attachments.put(attachment.getName(), attachment);
    }
    
    public int getNumberOfAttachments(){
        return this.attachments.size();
    }

    public String getSubject() {
        return subject;
    }

    public void setSubject(String subject) {
        this.subject = subject;
    }
    
}

TicketServlet.java:

package cn.example;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.LinkedHashMap;
import java.util.Map;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;

import jdk.nashorn.internal.ir.RuntimeNode.Request;
@WebServlet(
        name = "ticketServlet",
        urlPatterns = {"/tickets"},
        loadOnStartup = 1
)
//告诉web容器为该servlet提供文件上传支持
@MultipartConfig(
        // 告诉web容器文件必须达到5MB时才写入临时目录
        fileSizeThreshold = 5_242_800, // 5MB
        // 上传的文件不能超过20MB
        maxFileSize = 20_971_520L, // 20MB
        // 不能接收超过40MB的请求
        maxRequestSize = 41_942_040L // 40MB
)
public class TicketServlet extends HttpServlet{
    private volatile int TICKET_ID_SEQUENCE = 1;
    // 使用哈希map作为票据数据库
    private Map<Integer, Ticket> ticketDatabase = new LinkedHashMap<>();
    
    /*
     * 在doGet()方法中,根据请求参数的不同,把任务委托给相应的执行器
     * 功能:
     *         显示创建票据页面
     *         查看单个票据内容
     *         下载附件
     *         显示票据列表
     */
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf-8");
        resp.setCharacterEncoding("utf-8");
        
        String action = req.getParameter("action");
        // 若不带action请求参数,设置默认值,即默认的行为是显示票据列表
        if(action == null)
            action = "list";
        switch (action) {
            case "create":
                this.showTicketForm(req,resp);
                break;
            case "view":
                this.viewTicket(req, resp);
                break;
            case "download":
                this.downloadAttachment(req, resp);
                break;
            default:
                this.listTickets(req, resp);
                break;
        }
    }
    
    /*
     * 创建新的票据
     */
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf-8");
        resp.setCharacterEncoding("utf-8");
        
        String action = req.getParameter("action");
        if(action == null)
            action = "list";
        switch(action){
            case "create":
                this.createTicket(req, resp);
                break;
            case "list":
            default:
                resp.sendRedirect("tickets");
                break;
        }
    }
    
    private void createTicket(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException{
        
        //1.新建一个票据对象
        Ticket ticket = new Ticket();
        
        //2.以表单数据为源,设置票据相应的成员属性
        ticket.setCustomerName(req.getParameter("customerName"));
        ticket.setSubject(req.getParameter("subject"));
        ticket.setBody(req.getParameter("body"));
        
        //3.处理文件上传
        Part filePart = req.getPart("file1");
        if(filePart != null && filePart.getSize() > 0){
            Attachment attachment = this.processAttachment(filePart);
            if(attachment != null)
                ticket.addAttachments(attachment);
        }
        
        int id;
        synchronized (this) {
            id = this.TICKET_ID_SEQUENCE++;
            this.ticketDatabase.put(id, ticket);
        }
        
        resp.sendRedirect("tickets?action=view&ticketId=" + id);
    }
    
    private Attachment processAttachment(Part filePart) throws IOException{
        InputStream  inputStream = filePart.getInputStream();
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        
        int read;
        final byte[] bytes = new byte[1024];
        
        while((read = inputStream.read(bytes)) != -1){
            outputStream.write(bytes, 0, read);
        }
        
        Attachment attachment = new Attachment();
        attachment.setName(filePart.getSubmittedFileName());
        attachment.setContents(outputStream.toByteArray());
        
        return attachment;
    }
    
    private void listTickets(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
         request.setAttribute("ticketDatabase", this.ticketDatabase);
         request.getRequestDispatcher("/WEB-INF/jsp/view/listTickets.jsp").forward(request, response);
    }

    private void downloadAttachment(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String idString = req.getParameter("ticketId");
        Ticket ticket = this.getTicket(idString, resp);
        if(ticket == null)
            return;
        
        
        String name = req.getParameter("attachment");
        if(name == null){
            resp.sendRedirect("tickets?action=view&tickedId=" + idString);
            return;
        }
        
        Attachment attachment = ticket.getAttachment(name);
        if(attachment == null){
            resp.sendRedirect("tickets?action=view&tickedId=" + idString);
            return;
        }
        
        // 强制浏览器询问用户是保存还是下载文件,而不是在浏览器打开该文件
        resp.setHeader("Content-Disposition", "attachment; filename = " + attachment.getName());
        // 设置内容类型是通用的、二进制内容类型,这样容器就不会使用字符编码对该数据进行处理
        // 更加准确的应该使用附件的MIME内容类型
        resp.setContentType("application/octet-stream");
        
        // 使用ServletOutputStream将附件内容输出到响应中
        ServletOutputStream stream = resp.getOutputStream();
        stream.write(attachment.getContents());
    }

    private void viewTicket(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
        String idString = req.getParameter("ticketId");
        Ticket ticket = this.getTicket(idString, resp);
        if(ticket == null)
            return;
        
        req.setAttribute("ticketId", idString);
        req.setAttribute("ticket", ticket);
        
        RequestDispatcher dispatcher = req.getRequestDispatcher("/WEB-INF/jsp/view/viewTicket.jsp");
        dispatcher.forward(req, resp);
    }

    private Ticket getTicket(String idString, HttpServletResponse resp) throws IOException{
        if(idString == null || idString.length() == 0){
            resp.sendRedirect("tickets");
            return null;
        }
        try{
            Ticket ticket = this.ticketDatabase.get(Integer.parseInt(idString));
            if(ticket == null){
                resp.sendRedirect("tickets");
                return null;
            }
            return ticket;
        }catch(Exception e){
            resp.sendRedirect("tickets");
            return null;
        }
    }
    
    private void showTicketForm(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
           RequestDispatcher dispatcher = request.getRequestDispatcher("/WEB-INF/jsp/view/ticketForm.jsp");
           dispatcher.forward(request, response);
    }
}

 

运行结果:

空白的留言板:

创建留言:

单个留言的详细信息:

附件下载:

非空白的留言板:

 

分析:

   1.j在jsp可以结合java和html,方便编写动态页面

   2.在jsp中,可以做几乎所有java类可以完成的事情,这会带来不安全的操作。若jsp开发者不熟悉java,而他们无限制地在jsp中使用java,会带来安全隐患。

   3.表示层(jsp用于开发表示层)需要和业务逻辑层、数据持久层分割。

   4.在jsp中显示动态内容,应该尽量避免使用java代码,可以使用jsp标签库替代java代码

以上是关于Servlet实践--留言板-v1的主要内容,如果未能解决你的问题,请参考以下文章

无需重复代码的 REST api 最佳实践版本控制

gitlab 权限说明

将多个输出中的hls属性设置为单独的片段代码

Servlet 商品留言

刁肥宅数据结构课设“布隆过滤器的实践与应用”源代码(v1.0,永不上交)

Java Web项目_部门内部留言板