JAVA高并發(fā)秒殺API項(xiàng)目的學(xué)習(xí)筆記


一步一步的搭建JAVA WEB項(xiàng)目,采用Maven構(gòu)建,基于MYBatis+Spring+Spring MVC+Bootstrap技術(shù)的秒殺項(xiàng)目
學(xué)習(xí)的視頻:http://www.imooc.com/learn/587


創(chuàng)建Maven項(xiàng)目

  • 創(chuàng)建目錄,執(zhí)行Maven命令
 mvn archetype:generate -DgroupId=org.seckill -DartifactId=seckill -DarchetypeArtifactId=maven-archetype-webapp -DarchetypeCatalog=local

問題:Maven命令執(zhí)行到Generating Project in Batch mode 卡住,參考鏈接

  • 將項(xiàng)目導(dǎo)入到IDEA工具中
  • 修改項(xiàng)目配置
    1. 修改web.xml中的servlet版本,默認(rèn)是2.3,其不支持JSP的EL表達(dá)式。從Tomcat中的示例的web.xml中拷貝3.0的版本配置到項(xiàng)目中
    2. 補(bǔ)全目錄。項(xiàng)目的main目錄下創(chuàng)建java目錄,在src目錄下創(chuàng)建test目錄,test目錄下創(chuàng)建java和sources目錄
    3. 打開pom.xml,進(jìn)行依賴的配置
      • 單元測(cè)試依賴:Junit4
      • 日志依賴:slf4j+logback。(lf4j是規(guī)范/接口,log4j,common-logging,logback是日志的實(shí)現(xiàn))
      • 數(shù)據(jù)庫(kù)依賴:mysql-connector-java、c3p0
      • DAO框架:mybatis依賴:mybatis
      • Servlet web相關(guān)依賴:standard、jstl、jackson-databind、servlet-api
      • Spring依賴:spring-core、spring-beans、spring-context、spring-jdbc、spring-tx、spring-web、spring-webmvc、spring-test
        <dependencies>
          <!---3.0使用編程方式,4.0使用注解方式-->
          <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
          </dependency>
    
           <dependency>
               <groupId>org.slf4j</groupId>
               <artifactId>slf4j-api</artifactId>
               <version>1.7.12</version>
           </dependency>
            <dependency>
                <groupId>ch.qos.logback</groupId>
                 <artifactId>logback-core</artifactId>
                <version>1.1.1</version>
            </dependency>
            <!--實(shí)現(xiàn)slf4j接口并進(jìn)行整合-->
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-classic</artifactId>
                <version>1.1.1</version>
            </dependency>
            <!--數(shù)據(jù)庫(kù)相關(guān)依賴-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.35</version>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>c3p0</groupId>
                <artifactId>c3p0</artifactId>
                <version>0.9.1.2</version>
            </dependency>
            <!--DAO框架:mybatis依賴-->
            <dependency>
                  <groupId>org.mybatis</groupId>
                  <artifactId>mybatis</artifactId>
                  <version>3.3.0</version>
            </dependency>
            <!--mybatis自身實(shí)現(xiàn)的spring的整合依賴-->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis-spring</artifactId>
                <version>1.2.3</version>
            </dependency>
            <!--servlet web相關(guān)依賴-->
            <dependency>
                <groupId>taglibs</groupId>
                <artifactId>standard</artifactId>
                <version>1.1.2</version>
            </dependency>
            <dependency>
                <groupId>jstl</groupId>
                <artifactId>jstl</artifactId>
                <version>1.2</version>
            </dependency>
             <dependency>
                 <groupId>com.fasterxml.jackson.core</groupId>
                 <artifactId>jackson-databind</artifactId>
                 <version>2.5.4</version>
             </dependency>
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>javax.servlet-api</artifactId>
                <version>3.1.0</version>
            </dependency>
    
            <!--Spring依賴-->
            <!--spring核心-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-core</artifactId>
                <version>4.1.7.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-beans</artifactId>
                <version>4.1.7.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>4.1.7.RELEASE</version>
            </dependency>
            <!--spring的DAO層依賴-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jdbc</artifactId>
                <version>4.1.7.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-tx</artifactId>
                <version>4.1.7.RELEASE</version>
            </dependency>
            <!--spring的WEB層依賴-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-web</artifactId>
                <version>4.1.7.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc</artifactId>
                <version>4.1.7.RELEASE</version>
            </dependency>
            <!--spring的單元測(cè)試依賴-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-test</artifactId>
                <version>4.1.7.RELEASE</version>
            </dependency>
        </dependencies>
    

數(shù)據(jù)庫(kù)的設(shè)計(jì)

  • 在項(xiàng)目main目錄下創(chuàng)建sql目錄,新建 schema.sql,作為數(shù)據(jù)庫(kù)的創(chuàng)建腳本
  • 腳本代碼如下:
        -- 數(shù)據(jù)庫(kù)初始化腳本
    
        -- 創(chuàng)建數(shù)據(jù)庫(kù)
        CREATE DATABASE seckill;
    
        -- 使用數(shù)據(jù)庫(kù)
        use seckill;
    
        -- 創(chuàng)建秒殺庫(kù)存表:使用InnoDB引擎,其支持事務(wù)。主鍵自增設(shè)置為從1000開始,字符格式設(shè)置為UTF8
        CREATE TABLE seckill(
          seckill_id bigint NOT NULL AUTO_INCREMENT COMMENT '商品庫(kù)存id',
          name varchar(120) NOT NULL COMMENT '商品名稱',
          number int NOT NULL COMMENT '庫(kù)存數(shù)量',
          start_time timestamp NOT NULL COMMENT '秒殺開啟時(shí)間',
          end_time timestamp NOT NULL COMMENT '秒殺結(jié)束時(shí)間',
          create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時(shí)間',
            PRIMARY KEY (seckill_id),
          KEY idx_start_time(start_time),
          KEY idx_end_time(end_time),
          KEY idx_create_time(create_time)
        )ENGINE=InnoDB AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8 COMMENT='秒殺庫(kù)存表';
    
        -- 秒殺成功明細(xì)表
        CREATE TABLE success_killed(
          seckill_id bigint NOT NULL COMMENT '秒殺商品id',
          user_phone int NOT NULL COMMENT '用戶手機(jī)號(hào)',
          state tinyint NOT NULL  COMMENT '狀態(tài)標(biāo)示:-1指無效,0指成功,1指已付款',
          create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時(shí)間',
          PRIMARY KEY (seckill_id,user_phone),
          KEY idx_create_time(create_time)
        )ENGINE=InnoDB AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8 COMMENT='秒殺成功明細(xì)表';
    
        -- 初始化數(shù)據(jù)
        INSERT INTO seckill(name,number,start_time,end_time)
        VALUES
        ('1000元秒殺iphone6',100,'2016-06-28 00:00:00','2016-06-29 00:00:00'),
        ('500元秒殺iphone5',200,'2016-06-28 00:00:00','2016-06-29 00:00:00'),
        ('200元秒殺小米4',300,'2016-06-28 00:00:00','2016-06-29 00:00:00'),
        ('100元秒殺紅米note',400,'2016-06-28 00:00:00','2016-06-29 00:00:00');
    
    
        -- show create table seckill;
        -- 為什么手寫DDL,記錄每次上線的DDL修改
    

DAO實(shí)體和接口

  • 創(chuàng)建實(shí)體包org.seckill.entity

  • 創(chuàng)建DAO包org.seckill.dao

  • 創(chuàng)建SecKill實(shí)體類,生成getter和setter,重寫toString

        private long secKillId; 
        private String name; 
        private int number; 
        private Date startTime; 
        private Date endTime; 
        private Date createTime; 
    
  • 創(chuàng)建SuccessKilled實(shí)體類,生成getter和setter,重寫toString

        private long secKillId; 
        private long userPhone; 
        private short state; 
        private Date createTime; 
    
  • 創(chuàng)建DAO接口SecKillDao,添加減庫(kù)存,根據(jù)ID查詢秒殺對(duì)象,查詢秒殺商品列表方法

        /**
         * 減庫(kù)存
         * @param secKillId
         * @param killTime
         * @return如果影響行數(shù)大于1,表示更新的記錄行數(shù)
         */
        int reduceNumber(long secKillId,Date killTime);
    
        /**
         * 根據(jù)id查詢秒殺對(duì)象
         * @param secKillId
         * @return
         */
        SecKill queryById(long secKillId);
    
        /**
         * 根據(jù)偏移量查詢秒殺商品列表
         * @param offset
         * @param limit
         * @return
         */
        List<SecKill> queryAll(int offset,int limit);
    
  • 創(chuàng)建DAO接口SuccessKilledDao,添加插入購(gòu)買明細(xì),根據(jù)ID查詢購(gòu)買明細(xì)實(shí)體的方法

        /**
         * 插入購(gòu)買明細(xì),可過濾重復(fù)
         * @param secKillId
         * @param userPhone
         * @return插入的行數(shù)
         */
        int inertSuccessKilled(long secKillId,long userPhone);
    
        /**
         *根據(jù)ID查詢SuccessKilled并攜帶秒殺產(chǎn)品對(duì)象實(shí)體
         * @param secKillId
         * @return
         */
        SuccessKilled queryByIdWithSecKill(long secKillId);
    
  • 基于MyBaits實(shí)現(xiàn)DAO接口

    1. 創(chuàng)建mybatis-config.xml全局配置文件
     <?xml version="1.0" encoding="UTF-8" ?>
     <!DOCTYPE configuration
             PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
             "http://mybatis.org/dtd/mybatis-3-config.dtd">
     <configuration>
         <!--配置全局屬性-->
         <settings>
             <!--使用jdbc的getGeneratedKeys獲取數(shù)據(jù)庫(kù)自增主鍵值-->
             <setting name="useGenerateKeys" value="true"/>
    
             <!--使用列別名替換列名 默認(rèn)為true-->
             <setting name="useColumnLabel" value="true"/>
    
             <!--開啟駝峰命名轉(zhuǎn)換-->
             <setting name="mapUnderscoreCamelCase" value="true"
         </settings>
     </configuration>
    
    1. 創(chuàng)建mapper文件夾,用于存儲(chǔ)mybatis映射文件
    2. 創(chuàng)建SecKilledDao.xml映射文件
     <?xml version="1.0" encoding="UTF-8" ?>
     <!DOCTYPE mapper
             PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
     <mapper namespace="org.seckill.dao.SecKillDao">
          <!--為DAO接口方法提供sql語句配置-->
         <update id="reduceNumber">
             <!--具體的sql-->
             update
               seckill
             set
               number = number -1
             where seckill_id = #{secKillId}
             and start_time <![CDATA[ <= ]]> #{killTime}
             and end_time >= #{killTime}
             and number > 0;
         </update>
    
         <select id="queryById" resultType="SecKill" parameterType="long">
           select seckill_id,name,number,start_time,end_time,create_time
           from seckill
           where seckill_id = #{secKillId}
         </select>
    
         <select id="queryAll" resultType="SecKill">
           select seckill_id,name,number,start_time,end_time,create_time
           from seckill
           order by create_time desc
           limit #{offset},#{limit}
         </select>
     </mapper>
    
    1. 創(chuàng)建SuccessKilledDao.xml映射文件
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="org.seckill.dao.SuccessKilledDao">
        <!--為DAO接口方法提供sql語句配置-->
        <insert id="insertSuccessKilled">
           <!--ignore忽略主鍵沖突-->
           insert ignore into success_killed(seckill_id,user_phone)
           values (#{secKilled},#{userPhone})
        </insert>
    
        <select id="queryByIdWithSecKill" resultType="SuccessKilled">
          <!--根據(jù)id查詢seccessKilled并攜帶seckill實(shí)體 如何告訴mybatis把結(jié)果映射到successkilled同時(shí)映射到seckill屬性 mybatis可以自由控制sql-->
          select
            sk.seckill_id,
            sk.user_phone,
            sk.create_time,
            sk.state,
            s.seckill_id "seckill.seckill_id",
            s.name "seckill.name",
            s.number "seckill.number",
            s.start_time "seckill.start_time",
            s.end_time "seckill.end_time",
            s.create_time "seckill.crate_time"
          from success_killed sk
          inner join seckill s on sk.seckill_id = s.seckill_id
          where sk.seckill_id = #{secKillId}
        </select>
    </mapper> 
    
  • mybatis整合spring

    1. 創(chuàng)建spring文件,用于存儲(chǔ)spring配置文件
    2. 創(chuàng)建spring-dao.xml配置文件
    3. 創(chuàng)建jdbc.properties配置文件,用于存儲(chǔ)數(shù)據(jù)庫(kù)相關(guān)信息
    ``` 
      driver=com.mysql.jdbc.Driver
      url=jdbc:mysql://127.0.0.1:3306/seckill?useUnicode=true&characterEncoding=utf-8
      username=root
      password=purple
    ```
    
    1. 在spring-dao.xml配置文件中進(jìn)行四個(gè)步驟的配置
      • 配置數(shù)據(jù)庫(kù)相關(guān)參數(shù)
      • 配置數(shù)據(jù)庫(kù)連接池
      • 配置sqlSessionFactory對(duì)象
      • 配置掃描dao接口包,動(dòng)態(tài)實(shí)現(xiàn) dao接口,并注入到spring容器中
       <?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: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/context
              http://www.springframework.org/schema/context/spring-context-3.1.xsd">
               <!--配置整合mybatis過程-->
               <!--配置數(shù)據(jù)庫(kù)相關(guān)參數(shù)-->
               <context:property-placeholder location="classpath:jdbc.properties"/>
    
               <!--數(shù)據(jù)庫(kù)連接池-->
               <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
                   <property name="driverClass" value="${driver" />
                   <property name="jdbcUrl" value="${url}"/>
                   <property name="user" value="${username}"/>
                   <property name="password" value="${password}"/>
    
                   <property name="maxPoolSize" value="30"/>
                   <property name="minPoolSize" value="10"/>
                   <!--關(guān)閉連接后不自動(dòng)commit-->
                   <property name="autoCommitOnClose" value="false"/>
                   <!--獲取連接超時(shí)的時(shí)間-->
                   <property name="checkoutTimeout" value="1000"/>
                   <!--獲取連接失敗的重試次數(shù)-->
                   <property name="acquireRetryAttempts"   value="2"/>
               </bean>
    
               <!--配置sqlSessionFactory對(duì)象-->
               <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
                   <!--注入數(shù)據(jù)庫(kù)連接池-->
                   <property name="dataSource" value="dataSource"/>
                   <!--配置mybatis全局配置文件-->
                   <property name="configLocation" value="classpath:mybatis-config.xml"/>
                   <!--掃描entity包,使用別名-->
                   <property name="typeAliasesPackage" value="org.seckill.entity"/>
                   <!--掃描sql配置文件-->
                   <property name="mapperLocations" value="mapper/*.xml"/>
               </bean>
    
               <!--配置掃描dao接口包,動(dòng)態(tài)實(shí)現(xiàn) dao接口,并注入到spring容器中-->
               <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
                   <!--注入sqlSessionFactory對(duì)象-->
                   <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
                   <!--給出掃描Dao接口包-->
                   <property name="basePackage" value="org.seckill.dao"/>
               </bean>
       </beans>
    
  • Junit4與Spring進(jìn)行整合,進(jìn)行Junit4單元測(cè)試

    1. 創(chuàng)建SecKillDao的單元測(cè)試類
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:spring/spring-dao.xml")
    public class SecKillDaoTest {
        //注入DAO實(shí)現(xiàn)類依賴
        @Resource
        private SecKillDao secKillDao;
    
        @Test
        public void testReduceNumber() throws Exception {
            Date killTime = new Date();
            int result = secKillDao.reduceNumber(1000L,killTime);
            System.out.println(result);
    
        }
    
        @Test
        public void testQueryById() throws Exception {
            long id = 1000;
            SecKill secKill = secKillDao.queryById(id);
            System.out.println(secKill.getName());
        }
    
        @Test
        public void testQueryAll() throws Exception {
            List<SecKill> secKillList = secKillDao.queryAll(0,1000);
    
            for(SecKill row : secKillList){
                System.out.println(row.toString());
            }
        }
    }
    
    1. 創(chuàng)建SuccessKilledDao的單元測(cè)試類
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:spring/spring-dao.xml")
    public class SuccessKilledDaoTest {
        @Resource
        private SuccessKilledDao successKilledDao;
    
        @Test
        public void testInertSuccessKilled() throws Exception {
            int result = successKilledDao.insertSuccessKilled(1000L,28059830451L);
            System.out.println(result);
    
        }
    
        @Test
        public void testQueryByIdWithSecKill() throws Exception {
            SuccessKilled successKilled = successKilledDao.queryByIdWithSecKill(1000L,2147483647L);
            System.out.println(successKilled.toString());
        }
    } 
    
    1. 學(xué)習(xí)點(diǎn)
      • 單元測(cè)試類可以利用IDEA的快捷鍵,直接在要測(cè)試的類中進(jìn)行代碼的生成
      • mybatis的傳參,需要在DAO接口方法的形參中使用@Param注解進(jìn)行指明

業(yè)務(wù)層設(shè)計(jì)

  • 秒殺業(yè)務(wù)接口設(shè)計(jì)
    1. 創(chuàng)建業(yè)務(wù)包service

    2. 創(chuàng)建數(shù)據(jù)傳輸實(shí)體包dto

    3. 創(chuàng)建異常包exception

    4. 創(chuàng)建dto實(shí)體

      • 創(chuàng)建暴露秒殺地址DTO:Exposer
      public class Exposer {
          /**
           * 是否開啟秒殺
           */
          private boolean exposed;
      
          /**
           * 秒殺ID
           */
          private long secKillId;
      
          /**
           * 一種加密措施
           */
          private String md5;
      
          /**
           *系統(tǒng)當(dāng)前時(shí)間(毫秒值)
           */
          private long now;
      
          private long start;
      
          private long end;
      
          public Exposer(boolean exposed, String md5, long secKillId) {
              this.exposed = exposed;
              this.md5 = md5;
              this.secKillId = secKillId;
          }
      
          public Exposer(boolean exposed, long now, long start, long end) {
              this.exposed = exposed;
              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 long getSecKillId() {
              return secKillId;
          }
      
          public void setSecKillId(long secKillId) {
              this.secKillId = secKillId;
              this.secKillId = secKillId;
          }
      
          public String getMd5() {
              return md5;
          }
      
          public void setMd5(String md5) {
              this.md5 = md5;
          }
      
          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;
          }
      }
      
      
      • 創(chuàng)建封裝秒殺執(zhí)行后結(jié)果DTO:SecKillExecution
      public class SecKillExecution {
      
          private long secKillId;
      
          /**
           * 秒殺執(zhí)行結(jié)果狀態(tài)
           */
          private int state;
      
          /**
           * 狀態(tài)表示
           */
          private String stateInfo;
      
          private SuccessKilled successKilled;
      
          public SecKillExecution(long secKillId, int state, String stateInfo, SuccessKilled successKilled) {
              this.secKillId = secKillId;
              this.state = state;
              this.stateInfo = stateInfo;
              this.successKilled = successKilled;
          }
      
          public SecKillExecution(long secKillId, int state, String stateInfo) {
              this.secKillId = secKillId;
              this.state = state;
              this.stateInfo = stateInfo;
          }
      
          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 SuccessKilled getSuccessKilled() {
              return successKilled;
          }
      
          public void setSuccessKilled(SuccessKilled successKilled) {
              this.successKilled = successKilled;
          }
      }
      
    5. 創(chuàng)建異常類

      • 創(chuàng)建業(yè)務(wù)相關(guān)異常:SecKillException
      public class SecKillException extends RuntimeException{
          public SecKillException(String message) {
              super(message);
          }
      
          public SecKillException(String message, Throwable cause) {
              super(message, cause);
          }
      }
      
      • 創(chuàng)建重復(fù)秒殺異常類:RepeatKillException
      public class RepeatKillException extends SecKillException{
          public RepeatKillException(String message, Throwable cause) {
              super(message, cause);
          }
      
          public RepeatKillException(String message) {
              super(message);
          }
      }
      
      • 創(chuàng)建秒殺關(guān)閉異常類:SecKillCloseExce
        ption
      public class SecKillCloseException extends SecKillException{
          public SecKillCloseException(String message) {
              super(message);
          }
      
          public SecKillCloseException(String message, Throwable cause) {
              super(message, cause);
          }
      }
      
      
    6. 創(chuàng)建SecKillService業(yè)務(wù)接口:SecKillService

      • 創(chuàng)建查詢所有的秒殺記錄方法:getSecKillList
      • 創(chuàng)建查詢單個(gè)秒殺記錄方法:getById
      • 創(chuàng)建秒殺開啟時(shí)輸出秒殺接口地址方法:exportSecKillUrl
      • 創(chuàng)建執(zhí)行秒殺操作方法:executeSecKill
       public interface SecKillService {
           /**
            * 查詢所有的秒殺記錄
            * @return
            */
            List<SecKill> getSecKillList();
      
           /**
            * 查詢單個(gè)秒殺記錄
            * @param secKillId
            * @return
            */
           SecKill getById(long secKillId);
      
           /**
            * 秒殺開啟時(shí)輸出秒殺接口地址
            * 否則輸出系統(tǒng)時(shí)間和秒殺時(shí)間
            * 防止用戶猜測(cè)出秒殺地址的規(guī)律
            * @param secKillId
            */
           Exposer exportSecKillUrl(long secKillId);
      
           /**
            *執(zhí)行秒殺操作
            * @param secKillId
            * @param userPhone
            * @param md5
            */
           SecKillExecution executeSecKill(long secKillId,long userPhone,String md5) throws SecKillException,RepeatKillException,SecKillCloseException;
       }
      
    7. 業(yè)務(wù)接口設(shè)計(jì)的學(xué)習(xí)點(diǎn)

      • 站在使用者的角度進(jìn)行設(shè)計(jì)接口,不要冗余設(shè)計(jì)
      • 方法定義粒度,目的明確。非常友好的讓使用者調(diào)用接口
      • 參數(shù)要簡(jiǎn)煉
      • 返回類型要清晰
  • 秒殺業(yè)務(wù)接口實(shí)現(xiàn)

    1. 新建enums枚舉包,將數(shù)據(jù)字典放到枚舉中
    2. 在枚舉包下創(chuàng)建秒殺狀態(tài)枚舉:SecKillStatEnum
    public enum SecKillStatEnum {
        SUCCESS(1,"秒殺成功"),
        END(0,"秒殺結(jié)束"),
        REPEAT(-1,"重復(fù)秒殺"),
        INNER_ERROR(-2,"系統(tǒng)異常"),
        DATA_REWRITE(-3,"數(shù)據(jù)篡改");
    
        private int state;
        private String stateInfo;
    
        SecKillStatEnum(int state, String stateInfo) {
            this.state = state;
            this.stateInfo = stateInfo;
        }
    
        public int getState() {
            return state;
        }
    
        public String getStateInfo() {
            return stateInfo;
        }
    
        public static SecKillStatEnum stateOf(int index){
            for(SecKillStatEnum state : values()) {
                if(state.getState() == index){
                    return state;
                }
            }
    
            return null;
    
        }
    }
    
    1. 在service包下新建impl包
    2. 創(chuàng)建SecKillServiceImpl實(shí)現(xiàn)類,實(shí)現(xiàn)SecKillService接口方法
     public class SecKillServiceImpl implements SecKillService{
         private Logger logger = LoggerFactory.getLogger(SecKillService.class);
    
         private SecKillDao secKillDao;
         private SuccessKilledDao successKilledDao;
    
         //混淆字符,用于混淆MD5
         private final String salt = "sdlkjs#$#$dfowierlkjafdmv232k3j@@##$";
    
         @Override
         public List<SecKill> getSecKillList() {
             return secKillDao.queryAll(0,4);
         }
    
         @Override
         public SecKill getById(long secKillId) {
             return secKillDao.queryById(secKillId);
         }
    
         @Override
         public Exposer exportSecKillUrl(long secKillId) {
             SecKill secKill = secKillDao.queryById(secKillId);
    
             if(null == secKill){
                 return  new Exposer(false,secKillId);
             }
    
             Date startTime = secKill.getStartTime();
             Date endTime = secKill.getEndTime();
             Date nowTime = new Date();
    
             if(nowTime.getTime() < startTime.getTime()
                     || nowTime.getTime() > endTime.getTime()){
                 return new Exposer(false,secKillId,nowTime.getTime(),startTime.getTime(),endTime.getTime());
             }
    
             //轉(zhuǎn)化特定字符串的過程,不可逆
             String md5 = getMD5(secKillId);
    
             return new Exposer(true,md5,secKillId);
    
         }
    
         @Override
         public SecKillExecution executeSecKill(long secKillId, long userPhone, String md5)
                 throws SecKillException, RepeatKillException, SecKillCloseException {
             if(null == md5 || md5.equals(getMD5(secKillId))){
                 throw new SecKillException("seckill datarewirte");
             }
    
            try{
                //執(zhí)行秒殺邏輯,減庫(kù)存,記錄購(gòu)買行為
                Date nowTime = new Date();
                //減庫(kù)存
                int updateCount = secKillDao.reduceNumber(secKillId,nowTime);
    
                if(updateCount <= 0){
                    //沒有更新到記錄,秒殺結(jié)束
                    throw new SecKillCloseException("seckill is Closed");
                }else{
                    //記錄購(gòu)買行為
                    int insertCount = successKilledDao.insertSuccessKilled(secKillId,userPhone);
    
                    //唯一:secKillId,userPhone
                    if(insertCount <= 0){
                        //重復(fù)秒殺
                        throw new RepeatKillException("seckill repeated");
                    }else{
                        //秒殺成功
                        SuccessKilled successKilled = successKilledDao.queryByIdWithSecKill(secKillId,userPhone);
    
                        return new SecKillExecution(secKillId, SecKillStatEnum.SUCCESS,successKilled);
                    }
                }
            }catch(SecKillCloseException e1){
                throw e1;
            }catch(RepeatKillException e2){
                throw e2;
            }catch (Exception e){
                 logger.error(e.getMessage(),e);
                //所有編譯期異常,轉(zhuǎn)化為運(yùn)行期異常
                throw new SecKillException("seckill inner error:" + e.getMessage());
            }
         }
    
         /**
          * 生成MD5
          * @param secKillId
          * @return
          */
         private String getMD5(long secKillId){
             String base = secKillId + "/" + salt;
             String md5 = DigestUtils.md5DigestAsHex(base.getBytes());
             return md5;
         }
     }
    
  • 基于Spring托管Service實(shí)現(xiàn)類

    1. 創(chuàng)建Spring的service配置spring-service.xml,進(jìn)行service包下的注解類型的掃描配置
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           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/context
           http://www.springframework.org/schema/context/spring-context-3.1.xsd">
           <!--掃描service包下所有注解的類型-->
           <context:component-scan base-package="org.seckill.service"/>
    
    </beans> 
    
    1. 在service實(shí)現(xiàn)類中添加上@Service的注解,在類中的dao對(duì)象添加上@Autowired的注解
  • 配置并使用Spring聲明式事務(wù)

    1. 在spring-service.xml中添加上配置事務(wù)管理器
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--注入數(shù)據(jù)庫(kù)連接池-->
         <property name="dataSource" ref="dataSource"/>
    </bean>
    
    1. 在spring-service.xml中添加上配置基于注解的聲明式事務(wù)
    <tx:annotation-driven transaction-manager="transactionManager"/> 
    
    1. 在業(yè)務(wù)類的executeSecKill方法中添加上@Transactional事務(wù)注解
    2. 學(xué)習(xí)點(diǎn):使用注解控制事務(wù)方法的優(yōu)點(diǎn)
    • 開發(fā)團(tuán)隊(duì)達(dá)到一致約定,明確標(biāo)注事務(wù)方法的編程風(fēng)格
    • 保證事務(wù)方法的執(zhí)行時(shí)間盡可能短,不要穿插其他網(wǎng)絡(luò)操作RPC/HTTP請(qǐng)求,或者剝離到事務(wù)方法外部
    • 不是所有的方法都需要事務(wù),如只有一條修改操作,只讀操作就不需要事務(wù)控制
  • Service集成測(cè)試

    1. 添加上logback的日志配置文件logback.xml
     <?xml version="1.0" encoding="UTF-8"?>
    
     <configuration debug="true">
         <!-- ch.qos.logback.core.ConsoleAppender 控制臺(tái)輸出 -->
         <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
             <encoder>
                 <pattern>[%-5level] %d{HH:mm:ss.SSS} [%thread] %logger{36} - %msg%n</pattern>
             </encoder>
    
         </appender>
    
         <!-- 日志級(jí)別 -->
         <root>
             <level value="debug" />
             <appender-ref ref="STDOUT" />
         </root>
    
     </configuration>   
    
    1. 使用IDEA為SecKillService業(yè)務(wù)接口創(chuàng)建單元測(cè)試類SecKillServiceTest
    2. 編寫單元測(cè)試方法
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration({"classpath:spring/spring-dao.xml","classpath:spring/spring-service.xml"})
    public class SecKillServiceTest {
        private final Logger logger = LoggerFactory.getLogger(this.getClass());
        @Autowired
        private SecKillService secKillService;
        @Test
        public void testGetSecKillList() throws Exception {
            List<SecKill> list = secKillService.getSecKillList();
            logger.info("list={}",list);
        }
    
        @Test
        public void testGetById() throws Exception {
            SecKill secKill = secKillService.getById(1000L);
            logger.info("secKill:{}",secKill);
    
        }
    
        /**
         * 測(cè)試完整業(yè)務(wù),注意集成測(cè)試代碼完整邏輯,注意可重復(fù)執(zhí)行
         * @throws Exception
         */
        @Test
        public void testSecKillLogic() throws Exception {
            long id = 1000L;
            Exposer exposer = secKillService.exportSecKillUrl(id);
    
            if(exposer.isExposed()){
                logger.info("exposer={}",exposer);
                long phone = 18059830432L;
                SecKillExecution secKillExecution = secKillService.executeSecKill(id,phone,exposer.getMd5());
                logger.info("secKillExecution:{}",secKillExecution);
            }else{
                //秒殺未開始
                logger.warn("exposer={}",exposer);
            }
        }
    
        @Test
        public void testExportSecKillUrl() throws Exception {
            long id = 1000L;
            Exposer exposer = secKillService.exportSecKillUrl(id);
            logger.info("exposer={}",exposer);
    
        }
    
        @Test
        public void testExecuteSecKill() throws Exception {
            long id = 1000L;
            long phone = 18059830452L;
            String md5 = "f1974250b060f51c4a8e48df67232d53";
    
            SecKillExecution secKillExecution = secKillService.executeSecKill(id,phone,md5);
    
            logger.info("secKillExecution:{}",secKillExecution);
    
        }
    } 
    
    1. 單元測(cè)試的學(xué)習(xí)點(diǎn)
      • 集成測(cè)試的業(yè)務(wù)邏輯的完整性
      • 注意測(cè)試的可重復(fù)執(zhí)行

WEB層設(shè)計(jì)

  • 設(shè)計(jì)Restful接口

  • SpringMVC整合Spring

    1. 在web.xml中配置DispatcherServlet
    2. 創(chuàng)建web包
    3. 創(chuàng)建spring-web.xml配置文件
    4. 在spring-web.xml進(jìn)行SpringMVC的配置
      • 開啟SpringMVC注解模式
      • servlet-mapping映射路徑
      • 配置jsp顯示viewResolver
      • 掃描web相關(guān)的bean
      <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:conext="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">
             <!--配置Spring MVC-->
             <!--開啟SpringMVC注解模式-->
             <!--簡(jiǎn)化配置
              1、自動(dòng)注冊(cè)DefaultAnnotationHandlerMapping,AnnotationMethodHandlerAdapter
              2、提供一系列功能:數(shù)據(jù)綁定,數(shù)字和日期的轉(zhuǎn)化@NumberFormat,@DataTimeFormat
                  xml,json默認(rèn)讀寫支持
             -->
              <mvc:annotation-driven/>
      
              <!--servlet-mapping映射路徑-->
              <!--靜態(tài)資源默認(rèn)servlet配置
                  1、加入對(duì)靜態(tài)資源的處理:js,css,img
                  2、允許使用/做整體映射
              -->
              <mvc:default-servlet-handler/>
      
              <!--配置jsp顯示viewResolver-->
              <bean 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>
      
              <!--掃描web相關(guān)的bean-->
              <conext:component-scan base-package="org.seckill.web"/>
      
      </beans> 
      
  • 實(shí)現(xiàn)秒殺相關(guān)的Restful接口

    1. 創(chuàng)建控制類SecKillController,實(shí)現(xiàn)獲取列表,獲取單條數(shù)據(jù),獲取系統(tǒng)時(shí)間,獲取秒殺地址,秒殺的方法
     @Controller
     @RequestMapping("/seckill/")//模塊/資源
     public class SecKillController {
         private final Logger logger = LoggerFactory.getLogger(this.getClass());
         @Autowired
         private SecKillService secKillService;
    
        @RequestMapping(name="/list",method= RequestMethod.GET)
        public String list(Model model){
            List<SecKill> list = secKillService.getSecKillList();
            model.addAttribute("list",list);
            return "list";
        }
    
         @RequestMapping(value="/{secKillId}/detail",method=RequestMethod.GET)
         public String detail(@PathVariable("secKillId") Long secKillId,Model model){
             if(secKillId == null){
                 return "redirect:/seckill/list";
             }
    
             SecKill secKill = secKillService.getById(secKillId);
    
             if(secKill == null){
                 return "redirect:/seckill/list";
             }
    
             model.addAttribute("secKill",secKill);
             return "detail";
         }
    
         @RequestMapping(value="/{secKillId}/exposer",method = RequestMethod.POST,
             produces = {"application/json;charset=utf-8"})
         @ResponseBody
         public SecKillResult<Exposer> exposer(@PathVariable("secKillId") Long secKillId){
             SecKillResult<Exposer> result = null;
    
             try{
                 Exposer exposer = secKillService.exportSecKillUrl(secKillId);
                 result = new SecKillResult<Exposer>(true,exposer);
    
             }catch(Exception e){
                 logger.error(e.getMessage(),e);
                 result = new SecKillResult<Exposer>(false,e.getMessage());
             }
    
             return result;
         }
    
         @RequestMapping(value="/{secKillId}/{md5}/execution",
         method = RequestMethod.POST,
         produces = {"application/json;charset=utf-8"})
         public SecKillResult<SecKillExecution> excute(@PathVariable("secKillId") Long secKillId,
                                                       @PathVariable("md5") String md5,
                                                @CookieValue(value="killPhone",required = false) Long userPhone){
             //springmvc valid
             if(userPhone == null){
                 return new SecKillResult<SecKillExecution>(false,"未注冊(cè)");
             }
    
             SecKillResult<SecKillExecution> result = null;
    
             try{
                 SecKillExecution secKillExecution = secKillService.executeSecKill(secKillId,userPhone,md5);
                 result = new SecKillResult<SecKillExecution>(true,secKillExecution);
    
             }catch(RepeatKillException e){
                 SecKillExecution secKillExecution = new SecKillExecution(secKillId, SecKillStatEnum.REPEAT);
                 result = new SecKillResult<SecKillExecution>(false,secKillExecution);
    
             }catch(SecKillCloseException e){
                 SecKillExecution secKillExecution = new SecKillExecution(secKillId, SecKillStatEnum.END);
                 result = new SecKillResult<SecKillExecution>(false,secKillExecution);
    
             }catch(Exception e){
                 logger.error(e.getMessage(),e);
                 SecKillExecution secKillExecution = new SecKillExecution(secKillId, SecKillStatEnum.INNER_ERROR);
                 result = new SecKillResult<SecKillExecution>(false,secKillExecution);
             }
    
             return result;
         }
    
         @RequestMapping(value="/time/now",method=RequestMethod.GET)
         public SecKillResult<Long> time(){
             Date now = new Date();
             return new SecKillResult<Long>(true,now.getTime());
    
         }
     }
    
  • 基于Bootstrap開發(fā)頁面結(jié)構(gòu)
    1. 創(chuàng)建jsp文件夾,創(chuàng)建common/header.jsp,common/tag.jsp,list.jsp,detail.jsp,并引入bootstrap框架,jquery、cookie、countdown插件,可以從百度和bootcss的CDN中引入插件。

    2. 創(chuàng)建js文件seckill.js,進(jìn)行登錄、計(jì)時(shí)的交互邏輯的編碼,并在詳細(xì)頁面中引入

     var seckill = {
         //封裝秒殺相關(guān)ajax的url
         URL: {
           now: function(){
               return '/seckill/time/now';
           },
           exposer: function(id){
               return '/seckill/' + id + '/exposer';
           },
           execution : function(id,md5){
               return '/seckill/' + id + '/' + md5 + '/execution';
           }
         },
         //處理秒殺邏輯
         handleSecKillKill: function(secKillId,node){
             node.hide().html('<button class="btn btn-primary btn-lg" id="killBtn">開始秒殺</button>');
    
             $.post(seckill.URL.exposer(secKillId),{},function(result){
                 if(result && result.success){
                     var exposer = result.data;
    
                     if(exposer.exposed){
                         //開啟秒殺
                          //獲取秒殺地址
                         var killUrl =  seckill.URL.execution(secKillId,exposer.md5);
                         console.log('killUrl:',killUrl);
                         //綁定一次點(diǎn)擊事件
                         $('#killBtn').one('click',function(){
                              //執(zhí)行秒殺請(qǐng)求
                             $(this).addClass('disabled');
                             $.post(killUrl,{},function(result){
                                  if(result && result.success){
                                      var killResult = result.data;
                                      var state = killResult.state;
                                      var stateInfo = killResult.stateInfo;
    
                                      node.html('<span class="label label-success">'+stateInfo+'</span>');
                                  }
                             });
                         });
    
                         node.show();
                     }else{
                         //未開啟秒殺
                         //重新計(jì)算計(jì)時(shí)邏輯
                         seckill.countdown(secKillId,exposer.now,exposer.start,exposer.end);
                     }
    
                 }else{
                     console.error('result:',result);
                 }
             });
         },
         //計(jì)時(shí)
         countdown: function(secKillId,nowTime,startTime,endTime){
             var $secKillBox = $('#seckill-box');
    
             if(nowTime > endTime){
                 $secKillBox.html('秒殺結(jié)束');
             }else if(nowTime < startTime){
                 $secKillBox.html('秒殺未開始');
                 var killTime = new Date(startTime + 1000);
    
                 $secKillBox.countdown(killTime,function(event){
                     var format = event.strftime('秒殺倒計(jì)時(shí):%D天 %H時(shí) %M分 %S秒');
                     $secKillBox.html(format);
                 }).on('finish.countdown',function(){
                     //獲取秒殺地址,控制實(shí)現(xiàn)邏輯,執(zhí)行秒殺
                     seckill.handleSecKillKill(secKillId,$secKillBox);
                 });
             }else{
                 //秒殺開始
                 seckill.handleSecKillKill(secKillId,$secKillBox);
             }
    
    
         },
         //驗(yàn)證手機(jī)號(hào)
         validatePhone: function(phone){
             if(phone && phone.length == 11 && !isNaN(phone)){
                 return true;
             }else{
                 return false;
             }
         },
         //詳情頁秒殺邏輯
         detail: {
             //詳情頁初始化
             init: function(params){
                 //用戶手機(jī)驗(yàn)證和登錄,計(jì)時(shí)交互
                 //規(guī)劃交互流程
                 //在cookie中查找手機(jī)號(hào)
                 var killPhone = $.cookie('killPhone'),
                     startTime = params.startTime,
                     endTime = params.endTime,
                     secKillId = params.secKillId;
    
                 //驗(yàn)證手機(jī)號(hào)
                 if(!seckill.validatePhone(killPhone)){
                     var killPhoneModal = $('#killPhoneModal');
    
                     killPhoneModal.modal({
                         show: true,
                         backdrop: 'static',//禁止位置關(guān)閉
                         keyboard: false//關(guān)閉鍵盤事件
                     });
    
                     $('#killPhoneBtn').click(function(){
                        var inputPhone = $('#killPhoneKey').val();
                         if(seckill.validatePhone(inputPhone)){
                             //電話寫入cookie
                             $.cookie('killPhone',inputPhone,{expires:7,path: '/seckill'})
                             window.location.reload();
    
                         }else{
                             //正常下會(huì)有一個(gè)前端字典
                            $('#killPhoneMessage').hide().html('<label class="label label-danger">手機(jī)號(hào)碼錯(cuò)誤</label>').show(300);
                         }
                     });
                 }
    
                 //用戶已經(jīng)登錄
                 //計(jì)時(shí)交互
                 $.get(seckill.URL.now(),function(result){
                     if(result && result.success){
                         var nowTime = result.data;
                         seckill.countdown(secKillId,nowTime,startTime,endTime);
    
                     }else{
                         consolw.error('result:',result);
                     }
                 });
             }
    
         }
    
     }
    
    1. 在detail.jsp頁面中引入seckill.js文件,并進(jìn)行初始化
    <script type="text/javascript">
        $(function(){
           //使用EL表達(dá)式傳入?yún)?shù)
           seckill.detail.init({
             secKillId: ${secKill.secKillId},
             startTime: ${secKill.startTime.time},
             endTime: ${secKill.endTime.time}
           });
        });
    </script> 
    
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,283評(píng)論 6 530
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 97,947評(píng)論 3 413
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,094評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,485評(píng)論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,268評(píng)論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,817評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,906評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,039評(píng)論 0 285
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,551評(píng)論 1 331
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,502評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,662評(píng)論 1 366
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,188評(píng)論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,907評(píng)論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,304評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,563評(píng)論 1 281
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,255評(píng)論 3 389
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,637評(píng)論 2 370

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,775評(píng)論 18 139
  • 一、配置maven的pom.xml加載jar包 為了后續(xù)開發(fā)的方便,將SSM框架所有需要的jar包一并加載進(jìn)來 p...
    docki閱讀 2,268評(píng)論 1 23
  • 工作到現(xiàn)在快要1年半了,一直沒有時(shí)間自己從頭搭建個(gè)框架,這個(gè)周末實(shí)在是無聊,真的不想打lol了,(黑色玫瑰開黑的喊...
    MacSam閱讀 6,464評(píng)論 7 20
  • Spring 技術(shù)筆記Day 1 預(yù)熱知識(shí)一、 基本術(shù)語Blob類型,二進(jìn)制對(duì)象Object Graph:對(duì)象圖...
    OchardBird閱讀 988評(píng)論 0 2
  • 大家好,我是魔法仙女。 泡泡國(guó)王生了個(gè)可愛的泡泡公主,邀請(qǐng)我一起去城堡里參加派對(duì)呢!我要給泡泡公主送上我的魔法禮物...
    慕落雪閱讀 861評(píng)論 0 51