Customer-Support-v1

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 attachments = new LinkedHashMap<>();    //和附件是一对多

    public Attachment getAttachment(String name)
    {
        return this.attachments.get(name);
    }

    public Collection 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排除依赖。

       
            javax.servlet.jsp
            javax.servlet.jsp-api
            2.3.1
            provided
        

        
            javax.servlet.jsp.jstl
            javax.servlet.jsp.jstl-api
            1.2.1
            compile
        

        
            org.glassfish.web
            javax.servlet.jsp.jstl
            1.2.2
            compile
            
                
                    javax.servlet
                    servlet-api
                
                
                    javax.servlet.jsp
                    jsp-api
                
                
                    javax.servlet.jsp.jstl
                    jstl-api
                
            
        
3.5、jsp

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

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


        
            *.jsp
            *.jspf
            utf-8
            /WEB-INF/jsp/base.jsp
            true
            text/html
        
    

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



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 ticketDatabase =
            (Map)request.getAttribute("ticketDatabase");
%>



    Customer Support


Tickets

">Create Ticket

<% if(ticketDatabase.size() == 0) { %>There are no tickets in the system.<% } else { for(int id : ticketDatabase.keySet()) { String idString = Integer.toString(id); Ticket ticket = ticketDatabase.get(id); %>Ticket #<%= idString %>: "><%= ticket.getSubject() %> (customer: <%= ticket.getCustomerName() %>)
<% } } %>

viewTicket.jsp

<%@ page session="false" %>
<%
    String ticketId = (String)request.getAttribute("ticketId");
    Ticket ticket = (Ticket)request.getAttribute("ticket");
%>



    Customer Support


Ticket #<%= ticketId %>: <%= ticket.getSubject() %>

Customer Name - <%= ticket.getCustomerName() %>

<%= ticket.getBody() %>

<% if(ticket.getNumberOfAttachments() > 0) { %>Attachments: <% int i = 0; for(Attachment a : ticket.getAttachments()) { if(i++ > 0) out.print(", "); %> "><%= a.getName() %><% } %>

<% } %> ">Return to list tickets

ticketForm.jsp

  • multipart/form-data表明该表单可以接受上传
  • hidden不会在页面实现出来,但是提交表单的时候还是会传给后台
<%@ page session="false" %>



    Customer Support


Create a Ticket

Your Name


Subject


Body


Attachments


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 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)