SSM 框架搭建 sckill 秒杀系统 —— step.4 web控制层建设

索引:

  1. SSM 框架搭建 sckill (秒杀系统) —— step.1 前期准备 + 框架配置
  2. SSM 框架搭建 sckill (秒杀系统) —— step.2 持久层建设
  3. SSM 框架搭建 sckill 秒杀系统 —— step.3 业务逻辑层建设
  4. SSM 框架搭建 sckill 秒杀系统 —— step.4 web控制层建设
  5. SSM 框架搭建 sckill 秒杀系统 —— step.5 web展示层建设

一、web 控制层(controller)简介

        首先申明一点:在Java-Web 项目中, controller 控制层的定义并非独立的层级,而是 web 浏览器 view (表现层)的一个控制器集合,负责 view 层的流程控制,并通过连接和调用 Service 层,实现系统的逻辑业务。(严格意义上讲,应该把 controller 层归属到前端 MVC 设计框架中的一环。不过为了方便归纳,将其单独拎出来作为一个层级来描述。)

二、Controller 层搭建

       如图所示,Controller 层的代码被归类到 web 文件夹下。这里说明一下,对于controller 层的文件夹命名并没有什么特殊的规定,从网上的各种教程来看,在 SSM 框架中,对于 Controller 层的命名,有直接命名为 controller 的,也有命名为 web 的,没有太大影响,只要在 spring 配置的时候写对路径就好。
在这里插入图片描述
       由于这个秒杀系统较为简单,更加专注于核心的秒杀逻辑而没有关注类似于登录注销一类的功能,因此在 controller 层的设计上,显得较为简单只有一个 controller 用于控制页面的各项业务逻辑。
       在代码编写过程中用到的的注解有:

  1. @Controller :该注解表示将 controller 的代码放入 Spring 的容器中,方便Spring 进行自动化管理。(此处需要注意区分 @Service@Controller 。在 service 层,使用 @Service 来告诉 Spring 容器 service 的接口的实现类是什么,方便 Spring 对 service 的接口和实现类进行匹配和调度。而在 controller 层,@Controller 的作用在于告诉 Spring,当页面返回请求时,应当在含有 @Controller 注解的类中,寻找对应的业务逻辑关系。
  2. @RequestMapping() : 该注解传递的参数为 value=URL值, method=请求方式, preoduces=“传输的数据类型”。在视频中,使用的 value 只有一个值,不过实际上,value 也可以传递一组 URL 值用以说明此类 URL 均由该类(方法)进行逻辑流程处理。
  3. @Responsebody : 该注释表返回的参数为 json 格式的数据。此处需要注意,在敲代码时,容易将该注释与 @Requestbody 的弄混。虽然 @Responsebody@Requestbody 长得很像,而且通常都是用于处理 json 类型的数据格式,但两者之间的功能截然不同。@Responsebody@Requestbody 区别在于,@Responsebody 用于声明 response 方式的返回对象是 json 格式的,而 @Requestbody 的作用在于接收并解析页面传输的 json 数据。
  4. @PathVariable() : 该注解的作用是提取 URL 地址中使用 {valName} 方式传递的地址参数。

以下是 SeckillController 的编码:

package org.seckill.web;

import org.seckill.dto.Exposer;
import org.seckill.dto.SeckillExecution;
import org.seckill.dto.SeckillResult;
import org.seckill.entity.SeckillTab;
import org.seckill.enums.SeckillStatEnum;
import org.seckill.exception.RepeatKillException;
import org.seckill.exception.SeckillCloseException;
import org.seckill.service.SeckillService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import java.util.Date;
import java.util.List;


// 将当前 controller 放入 Spring 容器中
@Controller
@RequestMapping("/seckill")  // 代表模块 ——@RequestMapping value参数为资源 url: /模块 /资源 /{id} /细分
public class SeckillController {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    @Autowired  // 对类成员变量、方法及构造函数进行标注,完成自动装配工作
    private SeckillService seckillService;

    // 不满足该 RequestMethod.GET 的请求全部驳回
    @RequestMapping(value = "/list", method = RequestMethod.GET)
    public String list(Model model) {  // Model 用以存放数据
        // 获取列表页
        List<SeckillTab> list = seckillService.getSeckillList();
        // model 用以存放数据,addAttribute() 用以在model中添加标识和数据
        model.addAttribute("list", list);

        // list.jsp + model = ModelAndView
        return "list";  // 简写:/WEB-INF/jsp/"list".jsp
    }

    @RequestMapping(value = "/{seckillId}/detail", method = RequestMethod.GET)
    public String detail(@PathVariable("seckillId") Long seckillId, Model model) {
        if (seckillId == null) {
            // redirect:  请求重定向 /seckill -模块 /list -二级请求,重定向到list.jsp
            return "redirect:/seckill/list";
        }
        SeckillTab seckillTab = seckillService.getSeckillById(seckillId);
        if (seckillTab == null) {
            // 请求转发
            return "forward:/seckill/list";
        }
        model.addAttribute("seckillTab", seckillTab);

        return "detail";
    }

    // ajax json
    @RequestMapping(value = "/{seckillId}/exposer",
            method = RequestMethod.POST,
            produces = {"application/json;charset=UTF-8"})  // 告诉浏览器 content type:指明json ;指定传输 charset ,防止出现乱码
    @ResponseBody  // 注解 表示返回json 类型
    public SeckillResult<Exposer> exposer(@PathVariable("seckillId") Long seckillId) {
        SeckillResult<Exposer> result;
        try {
            Exposer exposer = seckillService.exportSeckillUrl((seckillId));
            result = new SeckillResult<Exposer>(true, exposer);
        } catch (Exception e) {
            logger.error(e.getMessage());
            result = new SeckillResult<Exposer>(false, e.getMessage());
        }
        return result;
    }

    @RequestMapping(value = "/{seckillId}/{md5}/execution",
            method = RequestMethod.POST,
            produces = {"applilcation/json;charset=UTF-8"})
    @ResponseBody  // 注解 表示返回json 类型
    public SeckillResult<SeckillExecution> execute(@PathVariable("seckillId") Long seckillId,
                                                   @PathVariable("md5") String md5,
                                                   @CookieValue(value = "killPhone", required = false) Long phone) {
        // CookieValue 当value值不存在时,系统会直接报错,因此使用 required= false 表请求并非必须

        if (phone == null) {
            return new SeckillResult<SeckillExecution>(false, "未注册");
        }
        SeckillExecution execution = null;
        try {
            execution = seckillService.executeSeckill(seckillId, phone, md5);
            return new SeckillResult<SeckillExecution>(true, execution);
        } catch (RepeatKillException er) {
            execution = new SeckillExecution(seckillId, SeckillStatEnum.REPEAT_KILL);
            return new SeckillResult<SeckillExecution>(false, execution);
        } catch (SeckillCloseException es) {
            execution = new SeckillExecution(seckillId, SeckillStatEnum.END);
            return new SeckillResult<SeckillExecution>(false, execution);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            execution = new SeckillExecution(seckillId, SeckillStatEnum.INNER_ERROR);
            return new SeckillResult<SeckillExecution>(false, execution);
        }
    }

    @RequestMapping(value = "/time/now", method = RequestMethod.GET)
    @ResponseBody
    public SeckillResult<Long> time() {
        Date now = new Date();
        return new SeckillResult(true, now.getTime());
    }
}

       由于本 sckill 秒杀系统的流程较为简单。因此,controller 的代码编写完毕。由于 controller 层的代码属于业务流程型处理代码,因此其测试方法应该与前端页面组合到一起时进行测试。

三、controller 层的 Spring + web.xml 配置

       同样的,Java 代码编写完毕后需要将 controller 层整合入 Spring 中,在 resources/spring 路径下,创建 spring-web.xml 文件,进行 controller 层的配置。配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
						   http://www.springframework.org/schema/beans/spring-beans.xsd
						   http://www.springframework.org/schema/mvc
						   http://www.springframework.org/schema/mvc/spring-mvc.xsd
						   http://www.springframework.org/schema/context
						   http://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 配置 SpringMVC -->
    <!-- 1:开启SpringMVC注解模式 -->
    <!--    简化配置:
        (1)自动注册DefaultAnnotationHandlerMapping, AnnotationMethodHandlerAdapter
        (2)提供一系列:数据绑定,数字和日期的format @NumberFormat, @DataTimgForimat,
            xml, json 默认读写支持
    -->

    <mvc:annotation-driven/>

    <!-- servlet-mapping 映射路径:“/” -->
    <!-- 2:静态资源默认servlet配置
        1、 加入对静态资源的处理:js, gif, png
        2、允许使用 “/” 做整体映射
     -->
    <mvc:default-servlet-handler/>

    <!-- 3:配置 jsp 显示 ViewResolver,配置视图解析器 -->
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
        <!-- 此处需要注意的一点是,由于此处的配置了逻辑视图的前缀和后缀,
        因此在 controller 中,return 返回的可以为简写的名称。而不是由于 
        controller 中使用了简写,此处才进行逻辑视图前缀和后缀的配置。 -->
        <!-- 配置逻辑视图的前缀 -->
        <property name="prefix" value="/WEB-INF/jsp/" />
        <!-- 配置逻辑视图的后缀 -->
        <property name="suffix" value=".jsp"/>
    </bean>
    
    <!--扫描Controller,并将其生命周期纳入Spring管理-->
    <context:annotation-config/>

    <!-- 4:扫描 web 相关的bean -->
    <context:component-scan base-package="org.seckill.web" />
</beans>

       对应的,web.xml 配置如下:

<?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_4_0.xsd"
         version="4.0"
         metadata-complete="true">
    <!-- 修改servlet版本 -->
    <!-- 用maven默认创建的web.xml无法使用,需要换成Tomcat样例中的web.xml  -->

    <!-- 配置 DispatcherServlet 前端控制器 -->
    <servlet>
        <servlet-name>seckill-dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 配置springMVC需要加载的配置文件
            spring-dao.xml, spring-service.xml, spring-web.xml
            MyBatis -> spring -> springMVC
         -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring/spring-*.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>seckill-dispatcher</servlet-name>
        <!-- ’/‘ ->特殊映射 默认匹配所有的请求  -->
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <!-- 处理POST提交乱码问题 -->
    <filter>
        <filter-name>encoding</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encoding</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

</web-app>

       此时,以 jsp 作为 view 渲染界面的 Spring-web 配置已经完成。

SSM 框架的 HTML - view 配置(题外话)

       在 SSM 框架中, SpringMVC 默认的前端展示方式是以 jsp 格式的页面进行展示,配置的依赖,视图解析器的配置为:

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
    <!-- 配置逻辑视图的前缀 -->
    <property name="prefix" value="/WEB-INF/jsp/" />
    <!-- 配置逻辑视图的后缀 -->
    <property name="suffix" value=".jsp"/>
</bean>

       虽然 jsp 很好用,但在很多时候,为了更好地实现前后端分离,我们通常采用 HTML 语言 + JavaScript + jQuery …… 实现前端的页面展示。(虽然我尝试了下,直接强行解析 HTML 文件似乎也没有什么不可以的,毕竟 jsp 页面的本质就是 HTML 语言 + jsp 语法。但似乎在解析速度上会有所减慢,因此还是实现常规的 HTML 视图解析器配置比较好。)

(1)在 pom.xml 中加入相关依赖

       找到 pom.xml 文件,添加依赖如下:

<!-- html view support -->
<!-- start apache -->
<dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.1.3</version>
</dependency>
<dependency>
    <groupId>commons-collections</groupId>
    <artifactId>commons-collections</artifactId>
    <version>3.2.1</version>
</dependency>
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3</version>
</dependency>
<!-- end apache -->
<!-- 注意!! org.springframework.ui.freemarker.FreeMarkerConfigurationFactory类
     在 spring-context-support 中,要配置 HTML 的 viewResolver 必须写
     而前面的那些配置,似乎可以省略
  -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>4.1.6.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.22</version>
</dependency>

       配置好并保存 pom.xml 之后,一般情况下 IDEA 和 eclipse 都会自动检测并加载未下载的依赖,如果不放心,也可以手动使用 “MAVEN Build” 工具手动添加依赖。

(2)Spring-web.xml 的 view 视图解析器配置

       配置的第一步就是将 jsp 视图解析器给删除(注释)掉,防止两个解析器之间产生冲突。之后,配置SSM 的 HTML 视图解析器,配置内容如下:

<!-- 配置 HTML 显示 viewResolver 视图解析器 -->
<!-- 这里配置的是freemarker -->
<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
    <!-- 注意:只有在maven项目的pom.xml文件中添加了 <artifactId>spring-context-support</artifactId> 依赖
        <property name=""> 中 name 才有属性值"templateLoaderPath"、"freemarkerSettings",不然就会报错
     -->
    <!-- 配置HTML文件路径 -->
    <property name="templateLoaderPath" value="/WEB-INF/views/"/>
    <property name="freemarkerSettings">
        <props>
            <prop key="template_update_delay">0</prop>
            <prop key="default_encoding">UTF-8</prop>
            <prop key="number_format">0.##########</prop>
            <prop key="datetime_format">yyyy-MM-dd HH:mm:ss</prop>
            <prop key="classic_compatible">true</prop>
            <prop key="template_exception_handler">ignore</prop>
        </props>
    </property>
</bean>
<bean id="freemarkerConfiguration" class="org.springframework.beans.factory.config.PropertiesFactoryBean"></bean>
<bean id="viewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
    <property name="exposeRequestAttributes" value="true" />
    <property name="exposeSessionAttributes" value="true" />
    <property name="viewClass">
        <value>org.springframework.web.servlet.view.freemarker.FreeMarkerView</value>
    </property>
    <property name="cache"><value>true</value></property>
    <!--这里需要注意一下,我注释了下面这样一行代码,这行代码的意思就是指引freemarker需要解析的文件的位置。注释掉原因是因为
     applicationContext.xml里有这样一行代码:<property name="templateLoaderPath" value="/WEB-INF/views/" /> 已经指定了视图位置。如果我们这里依然保留下方代码,页面回报406的找不到的错误 -->
    <!-- html视图解析器 必须先配置freemarkerConfig,注意html是没有prefix前缀属性的 -->
    <!--<property name="prefix"><value>/WEB-INF/views/</value></property>-->
    <property name="suffix"><value>.html</value></property>
    <property name="contentType">
        <value>text/html; charset=UTF-8</value>
    </property>
    <!-- order属性进行设置 系统首先按解析器的order值进行查找 首先使用FreeMarkerViewResolver解析器调用canHandle方法,
    判断当前解析器对视图是否能够解析。如不能解析在依次调用其他的。
    同时创建两个视图解析器的时候,freemarker一定要加order,且优先级为0(最高),否则解析HTML时会报错 -->
    <property name="order" value="0" />
</bean>

四、 DTO (数据传输对象)的规划

        在本系统中,系统的数据存储以 DTO (Data - Transfer - Object) 进行管理,通过 DTO 将数据进行封装与传递,保证了数据接口的一致性。
        同样的,在根目录下设立 dto 目录,用以存放相关的 dto 类。
在这里插入图片描述
        由于在系统中,共需要用到 “秒杀 id ” 对象,“秒杀结果查询”对象,以及对 json 数据进行封装的对象。在 DTO 中,共设立 :Exposer, SeckillExecution, SeckillResult 三个类。封装如下:

  1. Exposer 类:
package org.seckill.dto;

/**
 * 秒杀地址DTO
 */
public class Exposer {
    private  boolean exposed;    // 布尔值 : 是否给予用户秒杀地址
    private String md5;          // 一种加密措施(算法)
    private long seckillId;
    private long now;            // 系统当前时间(毫秒)
    private long start;          // 秒杀开启时间
    private long end;            // 秒杀结束时间

    public Exposer() {}
    public Exposer(boolean exposed, String md5, long seckillId) {
        this.exposed = exposed;
        this.md5 = md5;
        this.seckillId = seckillId;
    }

    public Exposer(boolean exposed, long seckillId, long now, long start, long end) {
        this.exposed = exposed;
        this.seckillId = seckillId;
        this.now = now;
        this.start = start;
        this.end = end;
    }

    public Exposer(boolean exposed, long seckillId) {
        this.exposed = exposed;
        this.seckillId = seckillId;
    }

    public boolean isExposed() {
        return exposed;
    }

    public void setExposed(boolean exposed) {
        this.exposed = exposed;
    }

    public String getMd5() {
        return md5;
    }

    public void setMd5(String md5) {
        this.md5 = md5;
    }

    public long getSeckillId() {
        return seckillId;
    }

    public void setSeckillId(long seckillId) {
        this.seckillId = seckillId;
    }

    public long getNow() {
        return now;
    }

    public void setNow(long now) {
        this.now = now;
    }

    public long getStart() {
        return start;
    }

    public void setStart(long start) {
        this.start = start;
    }

    public long getEnd() {
        return end;
    }

    public void setEnd(long end) {
        this.end = end;
    }

    @Override
    public String toString() {
        return "Exposer{" +
                "exposed=" + exposed +
                ", md5='" + md5 + '\'' +
                ", seckillId=" + seckillId +
                ", now=" + now +
                ", start=" + start +
                ", end=" + end +
                '}';
    }
}
  1. SeckillExecution 类:
package org.seckill.dto;

import org.seckill.entity.SucKilled;
import org.seckill.enums.SeckillStatEnum;

/**
 * 封装秒杀后执行的结果
 */
public class SeckillExecution {
    private long seckillId;
    private int state;            // 秒杀状态
    private String stateInfo;     // 秒杀状态描述
    private SucKilled sucKilled;  // 秒杀成功对象

    public SeckillExecution() {}
    public SeckillExecution(long seckillId, SeckillStatEnum seckillStatEnum, SucKilled sucKilled) {
        this.seckillId = seckillId;
        this.state = seckillStatEnum.getState();
        this.stateInfo = seckillStatEnum.getStateInfo();
        this.sucKilled = sucKilled;
    }
    public SeckillExecution(long seckillId, SeckillStatEnum seckillStatEnum) {
        this.seckillId = seckillId;
        this.state = seckillStatEnum.getState();
        this.stateInfo = seckillStatEnum.getStateInfo();
    }

    public long getSeckillId() {
        return seckillId;
    }

    public void setSeckillId(long seckillId) {
        this.seckillId = seckillId;
    }

    public int getState() {
        return state;
    }

    public void setState(int state) {
        this.state = state;
    }

    public String getStateInfo() {
        return stateInfo;
    }

    public void setStateInfo(String stateInfo) {
        this.stateInfo = stateInfo;
    }

    public SucKilled getSucKilled() {
        return sucKilled;
    }

    public void setSucKilled(SucKilled sucKilled) {
        this.sucKilled = sucKilled;
    }

    @Override
    public String toString() {
        return "SeckillExecution{" +
                "seckillId=" + seckillId +
                ", state=" + state +
                ", stateInfo='" + stateInfo + '\'' +
                ", sucKilled=" + sucKilled +
                '}';
    }
}
  1. SeckillResult 类:
package org.seckill.dto;

// 所有ajax请求返回类型: 封装 json 结果

public class SeckillResult<T> {
    private boolean success;
    private T data;
    private String error;

    public SeckillResult(){}
    public SeckillResult(boolean success, T data) {
        this.success = success;
        this.data = data;
    }

    public SeckillResult(boolean success, String error) {
        this.success = success;
        this.error = error;
    }

    public boolean isSuccess() {
        return success;
    }

    public T getData() {
        return data;
    }

    public String getError() {
        return error;
    }
}

       至此,web 控制层(controller)构建完毕。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

揽月泛夜舟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值