美文网首页
Customer-Support-v1

Customer-Support-v1

作者: shenyoujian | 来源:发表于2018-08-05 11:14 被阅读7次

    1、项目需求

    为该公司网站中添加一个交互性的客户支持应用程序。它需要能够让用户提出问题或支持票据,并且员工也可以对这些查询做出响应。支持票据和评论都应该支持文件附件。对于紧急的事件,客户能够进入一个有特定客户支持代表参与的聊天窗口。并且最重要的是,作为跨国公司的网站,整个应用程序能够实现本地化,要求支持公司所需要的所有语言,并且该应用程序还必须非常安全。这些要求并不过分,对吧,明天上线ok。哈哈哈哈

    2、创建项目

    2.1、启动idea创建maven项目
    image.png
    image.png

    选择maven和配置文件


    image.png
    工程名和项目路径
    image.png

    之后点击完成。

    为了在pom.xml文件中添加依赖之后自动引入jar,点击右下角红圈的Enable Auto-Import选项


    image.png

    配置全局tomcat


    image.png
    然后就能在Run/Debug Configurations里设置tomcat了
    (注意:如果指定了项目的url路径那么application context也要指定路径,见下图红圈)
    image.png
    image.png

    添加完成tomcat后点击运行,运行成功后如下图所示,项目部署成功
    (注意:请先在命令行停止已经运行的系统的tomcat服务,可使用命令systemctl stop tomcat8,否则无法启动idea的tomcat服务)


    image.png

    3、Customer-Support-v1

    3.1、v1的功能,由三个页面组成,通过doGet处理,一个票据列表,一个创建票据的页面和一个查看单个票据的页面。还支持下载某个ticket票据文件的附件,以及接受POST请求用于创建新的票据。
    3.2、pom.xml加入servlet依赖,scope详解
    image.png
    3.3、POJO类

    首先右键main选择Mark Directory as选择Sources Root,这样才能创建类,创建pojo包并且创建Ticket类和Attachment类,如下

    /**
     * @Author ljs
     * @Description TODO
     * @Date 2018/8/3 23:05
     **/
    public class Attachment {
    
        private String name;
        
        private byte[] contents;
    
        //省略get和set
    }
    

    首先,一个票据可以有多个附件,然后这些附件有名字,所以我们创建一个LinkedMap键值对来存储而不是一个List,这样就可以通过名字(键)来获取某个附件。这里先不写dao层,所有增查功能对应的方法写在model类里。主要有四个接口:

    • getAttachment(String name),通过附件名字来获取单个附件,
    • getAttachments(),这里是返回所有的附件,注意返回类型是一个集合Collection而不是一个Map,因为我们只需要返回附件实例,而不需要返回它们对应的名字,附件实例里其实已经有属性name。
    • addAttachment(Attachment attachment)添加附件
    • getNumberOfAttachments() 返回该票据附件的个数
      注意在这个类里的我们对attachments这个私有属性开放接口不再是get和set,而是addAttachment,getAttachment,getAttachmentsget和NumberOfAttachments,所以attachments没有get和set方法。
    /**
     * @Author ljs
     * @Description TODO
     * @Date 2018/8/3 22:56
     **/
    public class Ticket {
        private String customerName;        //票据名
    
        private String subject;             //票类型
    
        private String body;
    
        private Map<String, Attachment> attachments = new LinkedHashMap<>();    //和附件是一对多
    
        public Attachment getAttachment(String name)
        {
            return this.attachments.get(name);
        }
    
        public Collection<Attachment> getAttachments()
        {
            return this.attachments.values();
        }
    
        public void addAttachment(Attachment attachment)
        {
            this.attachments.put(attachment.getName(), attachment);
        }
    
        public int getNumberOfAttachments()
        {
            return this.attachments.size();
        }
        //省略get和set
    }
    
    3.4、视图层与控制层分离

    没分离之前向响应中输出动态的html代码全都写在servlet里的,非常不方便,所以使用jsp让业务逻辑与视图分离。首先加入依赖,因为jstl实现定义了相对旧版jsp和servlet规范的依赖,它们与当前版本的jsp和servlet规范的maven artifact id不同,所以使用exclusions将它们排除。也就是说加入了jstl依赖,但是这个依赖又依赖于旧版的jsp和servlet,根据maven的依赖传递,会把旧版的jsp和servlet依赖加入这个项目中,跟上面的servlet和jsp依赖冲突,所以使用exclusions排除依赖。

           <dependency>
                <groupId>javax.servlet.jsp</groupId>
                <artifactId>javax.servlet.jsp-api</artifactId>
                <version>2.3.1</version>
                <scope>provided</scope>
            </dependency>
    
            <dependency>
                <groupId>javax.servlet.jsp.jstl</groupId>
                <artifactId>javax.servlet.jsp.jstl-api</artifactId>
                <version>1.2.1</version>
                <scope>compile</scope>
            </dependency>
    
            <dependency>
                <groupId>org.glassfish.web</groupId>
                <artifactId>javax.servlet.jsp.jstl</artifactId>
                <version>1.2.2</version>
                <scope>compile</scope>
                <exclusions>
                    <exclusion>
                        <groupId>javax.servlet</groupId>
                        <artifactId>servlet-api</artifactId>
                    </exclusion>
                    <exclusion>
                        <groupId>javax.servlet.jsp</groupId>
                        <artifactId>jsp-api</artifactId>
                    </exclusion>
                    <exclusion>
                        <groupId>javax.servlet.jsp.jstl</groupId>
                        <artifactId>jstl-api</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
    
    3.5、jsp

    如果每个jsp都有相似的属性,那么在每个jsp文件的顶部重复添加page指令是非常麻烦的工作。我们可以在web.xml里设置通用的jsp属性。标签<jsp-config>是在<web-app>标签下的,该标签可以包含任意数目的<jsp-property-group>标签。

    下面这个属性组表示匹配项目中所有.jsp和.jspf的文件,把匹配到的所有jsp文件编码都设置为utf8类型为text/html,并且包含/WEB-INF/jsp/base.jspf这个jsp片段,<trim-directive-whitespaces>这个命令可以使jsp输出的html时去除多余的空行(jsp上使用EL和tag会产生大量的空格和空行)。

    <jsp-config>
            <jsp-property-group>
                <url-pattern>*.jsp</url-pattern>
                <url-pattern>*.jspf</url-pattern>
                <page-encoding>utf-8</page-encoding>
                <include-prelude>/WEB-INF/jsp/base.jsp</include-prelude>
                <trim-directive-whitespaces>true</trim-directive-whitespaces>
                <default-content-type>text/html</default-content-type>
            </jsp-property-group>
        </jsp-config>
    

    注意:intellij idea默认创建的web.xml版本为2.3版本,对应的jstl是1.1版本,最好替换为3.1版本的web.xml,对应的jstl是1.2版本是1.2版本,所以使用idea默认的web.xml是识别不了<jsp-config>标签的。修改如下重启:

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                                 http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
             version="3.1">
    

    base.jsp,1、导入类2、声明jstl核心代码库。

    <%@ page import="pojo.Ticket, pojo.Attachment" %>
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    

    listTickets.jsp
    1、禁止该jsp使用会话
    2、该页面需要遍历显示ticket,所以需要ticketDatabase,这个值在servlet的listTickets方法中设置,存储到请求中,当转发到该页面是,通过request.getAttribute()取出,因为getAttribute()返回Object对象,所以需要强制转换,将对象强制转换是一个未检查操作,所以需要抑制警告。

    <%@ page session="false" import="java.util.Map" %>
    <%
        @SuppressWarnings("unchecked")
        Map<Integer, Ticket> ticketDatabase =
                (Map<Integer, Ticket>)request.getAttribute("ticketDatabase");
    %>
    <!DOCTYPE html>
    <html>
    <head>
        <title>Customer Support</title>
    </head>
    <body>
    <h2>Tickets</h2>
    <a href="<c:url value="/tickets">
                <c:param name="action" value="create" />
            </c:url>">Create Ticket</a><br /><br />
    <%
        if(ticketDatabase.size() == 0)
        {
    %><i>There are no tickets in the system.</i><%
    }
    else
    {
        for(int id : ticketDatabase.keySet())
        {
            String idString = Integer.toString(id);
            Ticket ticket = ticketDatabase.get(id);
    %>Ticket #<%= idString %>: <a href="<c:url value="/tickets">
                            <c:param name="action" value="view" />
                            <c:param name="ticketId" value="<%= idString %>" />
                        </c:url>"><%= ticket.getSubject() %></a> (customer:
    <%= ticket.getCustomerName() %>)<br /><%
            }
        }
    %>
    </body>
    </html>
    

    viewTicket.jsp

    <%@ page session="false" %>
    <%
        String ticketId = (String)request.getAttribute("ticketId");
        Ticket ticket = (Ticket)request.getAttribute("ticket");
    %>
    <!DOCTYPE html>
    <html>
    <head>
        <title>Customer Support</title>
    </head>
    <body>
    <h2>Ticket #<%= ticketId %>: <%= ticket.getSubject() %></h2>
    <i>Customer Name - <%= ticket.getCustomerName() %></i><br /><br />
    <%= ticket.getBody() %><br /><br />
    <%
        if(ticket.getNumberOfAttachments() > 0)
        {
    %>Attachments: <%
        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 /><br /><%
        }
    %>
    <a href="<c:url value="/tickets" />">Return to list tickets</a>
    </body>
    </html>
    
    

    ticketForm.jsp

    • multipart/form-data表明该表单可以接受上传
    • hidden不会在页面实现出来,但是提交表单的时候还是会传给后台
    <%@ page session="false" %>
    <!DOCTYPE html>
    <html>
    <head>
        <title>Customer Support</title>
    </head>
    <body>
    <h2>Create a Ticket</h2>
    <form method="POST" action="tickets" enctype="multipart/form-data">
        <input type="hidden" name="action" value="create"/>
        Your Name<br/>
        <input type="text" name="customerName"><br/><br/>
        Subject<br/>
        <input type="text" name="subject"><br/><br/>
        Body<br/>
        <textarea name="body" rows="5" cols="30"></textarea><br/><br/>
        <b>Attachments</b><br/>
        <input type="file" name="file1"/><br/><br/>
        <input type="submit" value="Submit"/>
    </form>
    </body>
    </html>
    
    
    3.6、Servlet
    • 一个servlet可以通过请求参数action的不同分别调不同的相应方法。

    • showTicketForm,viewTicket,listTickets三个方法主要调用getRequestDispatcher转发到对应的三个jsp

    • createTicket方法,在提交表单post请求之后就调用该方法,该方法提取表单的数据封装成ticket对象并且存入map中,最后调用sendRedirect重定向到view。 使用getRequestDispatcher方法转发请求和使用 sendRedirect()方法重定向的区别

    • 保护共享资源,方法中创建的对象和变量在方法执行过程中的安全的,其他线程无法访问它。但是Servlet中的静态变量和实例变量都可以被多个线程访问的。所以当在使用这些变量的时候,对于多个请求(多线程)来说,可以使用同步代码块synchronized(this)来保证多个线程无法同时执行相同代码,使代码块具有排他性,避免出现多个ticketId相同而map是不重复的,会抛出异常。而且我们可以给变量加入volatile,避免一致性问题(其他线程读到变量修改之前的值)。

    • processAttachment,把表单提交的文件封装成attachment对象。先inputStream读到内存,然后再outputstream封装到对象的属性中。servlet3.1新增的getSubmittedFileName()识别文件上传之前的名字。

    • getTicket通过id获取对应的Ticket

    • downloadAttachment,Content-Disposition强制浏览器询问客户是保存还是下载,并且不会在线打开,google直接下载,360有询问。application/octet-stream,容器不会使用字符编码对该数据进行处理,最好还是使用MIME内容类型。最后使用servletOutputString将文件内容输出到相应中。

    @WebServlet(
            name = "ticketServlet",
            urlPatterns = {"/tickets"},
            loadOnStartup = 1
    )
    @MultipartConfig(
            fileSizeThreshold = 5_242_880, //5MB
            maxFileSize = 20_971_520L, //20MB
            maxRequestSize = 41_943_040L //40MB
    )
    public class TicketServlet extends HttpServlet
    {
        private volatile int TICKET_ID_SEQUENCE = 1;
    
        private Map<Integer, Ticket> ticketDatabase = new LinkedHashMap<>();
    
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException
        {
            String action = request.getParameter("action");
            if(action == null)
                action = "list";
            switch(action)
            {
                case "create":
                    this.showTicketForm(request, response);
                    break;
                case "view":
                    this.viewTicket(request, response);
                    break;
                case "download":
                    this.downloadAttachment(request, response);
                    break;
                case "list":
                default:
                    this.listTickets(request, response);
                    break;
            }
        }
    
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException
        {
            String action = request.getParameter("action");
            if(action == null)
                action = "list";
            switch(action)
            {
                case "create":
                    this.createTicket(request, response);
                    break;
                case "list":
                default:
                    response.sendRedirect("tickets");
                    break;
            }
        }
    
        private void showTicketForm(HttpServletRequest request,
                                    HttpServletResponse response)
                throws ServletException, IOException
        {
            request.getRequestDispatcher("/WEB-INF/jsp/view/ticketForm.jsp")
                   .forward(request, response);
        }
    
        private void viewTicket(HttpServletRequest request,
                                HttpServletResponse response)
                throws ServletException, IOException
        {
            String idString = request.getParameter("ticketId");
            Ticket ticket = this.getTicket(idString, response);
            if(ticket == null)
                return;
    
            request.setAttribute("ticketId", idString);
            request.setAttribute("ticket", ticket);
    
            request.getRequestDispatcher("/WEB-INF/jsp/view/viewTicket.jsp")
                   .forward(request, response);
        }
    
        private void downloadAttachment(HttpServletRequest request,
                                        HttpServletResponse response)
                throws ServletException, IOException
        {
            String idString = request.getParameter("ticketId");
            Ticket ticket = this.getTicket(idString, response);
            if(ticket == null)
                return;
    
            String name = request.getParameter("attachment");
            if(name == null)
            {
                response.sendRedirect("tickets?action=view&ticketId=" + idString);
                return;
            }
    
            Attachment attachment = ticket.getAttachment(name);
            if(attachment == null)
            {
                response.sendRedirect("tickets?action=view&ticketId=" + idString);
                return;
            }
    
            response.setHeader("Content-Disposition",
                    "attachment; filename=" + attachment.getName());
            response.setContentType("application/octet-stream");
    
            ServletOutputStream stream = response.getOutputStream();
            stream.write(attachment.getContents());
        }
    
        private void listTickets(HttpServletRequest request,
                                 HttpServletResponse response)
                throws ServletException, IOException
        {
            request.setAttribute("ticketDatabase", this.ticketDatabase);
    
            request.getRequestDispatcher("/WEB-INF/jsp/view/listTickets.jsp")
                    .forward(request, response);
        }
    
        private void createTicket(HttpServletRequest request,
                                  HttpServletResponse response)
                throws ServletException, IOException
        {
            Ticket ticket = new Ticket();
            ticket.setCustomerName(request.getParameter("customerName"));
            ticket.setSubject(request.getParameter("subject"));
            ticket.setBody(request.getParameter("body"));
    
            Part filePart = request.getPart("file1");
            if(filePart != null && filePart.getSize() > 0)
            {
                Attachment attachment = this.processAttachment(filePart);
                if(attachment != null)
                    ticket.addAttachment(attachment);
            }
    
            int id;
            synchronized(this)
            {
                id = this.TICKET_ID_SEQUENCE++;
                this.ticketDatabase.put(id, ticket);
            }
    
            response.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 Ticket getTicket(String idString, HttpServletResponse response)
                throws ServletException, IOException
        {
            if(idString == null || idString.length() == 0)
            {
                response.sendRedirect("tickets");
                return null;
            }
    
            try
            {
                Ticket ticket = this.ticketDatabase.get(Integer.parseInt(idString));
                if(ticket == null)
                {
                    response.sendRedirect("tickets");
                    return null;
                }
                return ticket;
            }
            catch(Exception e)
            {
                response.sendRedirect("tickets");
                return null;
            }
        }
    }
    
    
    3.7、上传到git

    相关文章

      网友评论

          本文标题:Customer-Support-v1

          本文链接:https://www.haomeiwen.com/subject/kbnuvftx.html