• 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代码

  • 相关阅读:
    LeetCode 48 Anagrams
    大数据实时处理:百分点实时计算架构和算法
    Kafka操作
    Kafka
    批量扫描互联网无线路由设备telnet,并获取WIFI密码
    WMI
    openvas
    原始套接字
    Zabbix
    MySQL exist
  • 原文地址:https://www.cnblogs.com/aristole/p/8026220.html
Copyright © 2020-2023  润新知