JAVA WEB快速入門之從編寫一個基於SpringBoot+Mybatis快速建立的REST API項目瞭解SpringBoot、SpringMVC REST API、Mybatis等相關知識

JAVA WEB快速入門系列以前的相關文章以下:(文章所有本人【夢在旅途原創】,文中內容可能部份圖片、代碼參照網上資源)javascript

第一篇:JAVA WEB快速入門之環境搭建css

第二篇:JAVA WEB快速入門之從編寫一個JSP WEB網站了解JSP WEB網站的基本結構、調試、部署html

第三篇:JAVA WEB快速入門之經過一個簡單的Spring項目瞭解Spring的核心(AOP、IOC)前端

第四篇:JAVA WEB快速入門之從編寫一個基於SpringMVC框架的網站了解Maven、SpringMVC、SpringJDBCvue

 

今天是第五篇,也是該系列文章的最後一篇,接上篇《JAVA WEB快速入門之從編寫一個基於SpringMVC框架的網站了解Maven、SpringMVC、SpringJDBC》,經過上篇文章的詳細介紹,知道如何使用maven來快速構建spring MVC應用,也可以使用spring MVC+springJDBC實現網站開發,而本文所涉及的知識則是在這基礎之上繼續提高,核心是講解如何使用spring boot來更快速的構建spring MVC,並經過mybatis及代碼生成相關DAO,同時利用VUE前端框架開發先後端分離的網站,用戶體驗更好,廢話很少說,直接進入本文主題。java

(提示:本文內容有點長,涉及的知識點也比較多,如果新手建議耐心看完!)git

1、建立Spring Boot+SpringMVC空項目github

  1.1經過https://start.spring.io/官網快速生成一個Spring Boot+SpringMVC空項目,以下圖示:web

(固然也能夠經過Eclipse或IDEA的Spring Boot插件來建立,可參見:http://www.javashuo.com/article/p-uwkbhixv-bz.htmlhttps://blog.csdn.net/qq_32572497/article/details/62037873spring

  

  設置後點擊頁面的生成項目按鈕,便可生成並下載spring boot項目代碼壓縮包,而後使用IDE導入存在的maven project便可。

  1.2調整項目,解決一些踩坑點

  1.2.1.調整spring boot App啓動類(如:SpringbootdemoApplication)到根包目錄或在啓動類上顯式添加@ComponentScan註解,並指定包路徑,以下代碼所示,cn.zuowenjun.boot是根包目錄,其他都是cn.zuowenjun.boot的子包

package cn.zuowenjun.boot;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;
//import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication //指定爲Spring Boot啓動入口,內含多個spring所須要的註解
@MapperScan(basePackages="cn.zuowenjun.boot.mapper")//設置Mybaits掃描的mapper包路徑
//@ComponentScan(basePackages= {"cn.zuowenjun.controller"}) //若是不在根包目錄,則需指定spring管理的相關包路徑
@EnableTransactionManagement //啓動事務管理
public class SpringbootdemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootdemoApplication.class, args);
    }

}

  1.2.2.解決POM文件報:

Description Resource Path Location Type
Execution default-resources of goal org.apache.maven.plugins:maven-resources-plugin:3.1.0:resources failed: Unable to load the mojo 'resources' (or one of its required components) from the plugin 'org.apache.maven.plugins:maven-resources-plugin:3.1.0'

直接在POM中添加以下resources依賴:

        <dependency>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-resources-plugin</artifactId>
            <version>2.5</version>
            <type>maven-plugin</type>
        </dependency>

  1.2.3.設置熱編譯啓動模式,以即可以隨時更改代碼後即時生效

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <optional>true</optional>
   </dependency>
</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <fork>true</fork>
            </configuration>
        </plugin>
   </plugins>
</build>

設置後項目的視圖就有以下顯示效果:

  1.3演示請求REST API分別返回JSON、XML

   建立好spring boot空項目環境後,咱們就能夠開始編寫相關代碼了,在此僅貼出實現了REST API分別響應返回JSON、XML格式的Controller,實現步驟以下:

   1.3.1在cn.zuowenjun.boot.controller包中建立DemoController,並編寫hellojson、helloxml Action方法,代碼以下:

package cn.zuowenjun.boot.controller;

import org.springframework.web.bind.annotation.*;

import cn.zuowenjun.boot.domain.*;

@RestController
public class DemoController {

    @RequestMapping(value="/hello/json",produces="application/json;charset=utf-8")
    public HelloDto hellojson()
    {
        HelloDto dto=new HelloDto();
        dto.setMessage("hello,zuowenjun.cn,hello java spring boot!");
        
        return dto;
    }
    
    @RequestMapping(value="/hello/xml",produces="text/xml;charset=utf-8")
    public HelloDto helloxml()
    {
        HelloDto dto=new HelloDto();
        dto.setMessage("hello,zuowenjun.cn,hello java spring boot!");
        
        return dto;
    }
}

如上代碼簡要說明:@RestController至關因而:@Controller、@ResponseBody,這個能夠查看@RestController註解類代碼就知道;@RequestMapping指定請求映射,其中produces設置響應內容格式(可理解爲服務端是生產者,而用戶在瀏覽器端【客戶端】是消費端),還有consumes屬性,這個是指可接收請求的內容格式(可理解爲用戶在瀏覽器端發送請求是消息的生產者,而服務端接收並處理該請求爲消息的消費者),固然還有其它一些屬性,你們能夠參見我上篇文章或網絡其它大神的相關文章加以瞭解。

另外須要注意,默認spring MVC只返回JSON格式,若需返回XML格式,還需添加XML JAR包依賴,以下:(能夠看到version這裏我指定了版本號區間,表示2.5.0及以上版本均可以,有些依賴spring-boot-starter-parent中都有提早配置依賴管理,咱們只須要指定groupId、artifactId便可,version就會使用spring boot中的默認版本,固然也能夠強制指定版本)

        <!-- 若是項目中須要REST API響應(返回)XML格式的報文體則應添加該依賴 -->
        <dependency>
            <groupId>com.fasterxml.jackson.jaxrs</groupId>
            <artifactId>jackson-jaxrs-xml-provider</artifactId>
            <version>[2.5.0,)</version><!--$NO-MVN-MAN-VER$ -->
        </dependency>

因爲項目中同時添加JSON及XML的JAR包,按照spring MVC的默認響應處理流程是:若是未指定produces,則當請求的header中指定了accept類型,則自動格式化並返回該accept所需的類型,若是未指定accept類型,則優先是響應XML,當找不到XML依賴包時纔會響應JSON,故若是項目中同時有JSON及XML,那麼最好顯式指定produces或者請求頭上指明accept類型 這一點與ASP.NET WEB API原理相同,由於都是符合REST架構風格的。

效果以下:

     

2、使用Mybatis框架完成Domain層、DAO層(這裏是Mapper層) ---提示:因爲篇幅有限,只貼出重點能體現不一樣知識點的代碼,其他能夠到GITHUB上查看下載源碼進行詳細瞭解

  2.0:首先在application.properties配置mybatis的相關選項,以下所示:

mybatis.type-aliases-package=cn.zuowenjun.boot.domain #包類型別名,這樣在XML中就能夠簡寫成類名
mybatis.config-location=classpath:mybatis/mybatis-config.xml #指定mybatis的配置文件路徑
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml #指定mapper XML的存放路徑

#這裏是使用SQL SERVER,若是是其它DB則使用其它驅動
spring.datasource.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver
spring.datasource.url=jdbc:sqlserver://DBIP:Port;DatabaseName=testDB
spring.datasource.username=dbuser
spring.datasource.password=dbpassword

   其次添加mybatis-spring-boot-starter maven依賴,它會自動添加相關的mybatis依賴包,配置以下:

        <!-- 添加 mybatis-spring-boot依賴,直接可使用mybatis環境操做DB-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>

  2.1全手寫JAVA代碼實現Mybatis的CRUD;

  2.1.1.在cn.zuowenjun.boot.domain包【實體模型或稱領域模型層,這裏算不上真正的領域模型,最多算是貧血的領域模型】中定義數據實體模型(Goods:商品信息),代碼以下:

package cn.zuowenjun.boot.domain;

import java.math.BigDecimal;
import java.util.Date;

public class Goods {
    private int id;
    private String title;
    private String picture;
    private BigDecimal price;
    private String introduction;
    private int categoryId;
    private String lastEditBy;
    private Date lastEditTime;
    
    public Goods() {
        
    }
    
    public Goods(int id,String title,String picture,
            BigDecimal price,String introduction,int categoryId,String lastEditBy,Date lastEditTime) {
        this.setId(id);
        this.setTitle(title);
        this.setPicture(picture);
        this.setPrice(price);
        this.setIntroduction(introduction);
        this.setCategoryId(categoryId);
        this.setLastEditBy(lastEditBy);
        this.setLastEditTime(lastEditTime);
    }
    
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    
    public String getPicture() {
        return picture;
    }
    public void setPicture(String picture) {
        this.picture = picture;
    }
    
    public BigDecimal getPrice() {
        return price;
    }
    
    public void setPrice(BigDecimal price) {
        this.price = price;
    }
    
    public String getIntroduction() {
        return introduction;
    }
    public void setIntroduction(String introduction) {
        this.introduction = introduction;
    }
    
    public int getCategoryId() {
        return categoryId;
    }
    public void setCategoryId(int categoryId) {
        this.categoryId = categoryId;
    }
    
    public String getLastEditBy() {
        return lastEditBy;
    }
    public void setLastEditBy(String lastEditBy) {
        this.lastEditBy = lastEditBy;
    }
    
    public Date getLastEditTime() {
        return lastEditTime;
    }
    public void setLastEditTime(Date lastEditTime) {
        this.lastEditTime = lastEditTime;
    }

}
View Code

  2.1.2.在cn.zuowenjun.boot.mapper包【數據映射處理層或稱DAO層】中定義數據映射處理接口及添加相應的SQL註解,以實現對數據進行CRUD,代碼以下:

package cn.zuowenjun.boot.mapper;

import java.util.*;

import org.apache.ibatis.annotations.*;

import cn.zuowenjun.boot.domain.*;

public interface GoodsMapper {
    
    @Select("select * from TA_TestGoods order by id offset (${pageNo}-1)*${pageSize} rows fetch next ${pageSize} rows only")
    List<Goods> getListByPage(int pageSize,int pageNo);
    
    @Select("select * from TA_TestGoods where categoryId=#{categoryId} order by id")
    List<Goods> getList(int categoryId);
    
    @Select("<script>select * from TA_TestGoods where id in " 
            +"<foreach item='item' index='index' collection='ids' open='(' separator=',' close=')'>#{item}</foreach>"
            +"order by id</script>")
    List<Goods> getListByMultIds(@Param("ids")int...ids);
    
    @Select("select * from TA_TestGoods where id=#{id}")
    Goods get(int id);
    

    @Insert(value="insert into TA_TestGoods(title, picture, price, introduction, categoryId, "
            + "lastEditBy, lastEditTime) values(#{title},#{picture},#{price},#{introduction},#{categoryId},#{lastEditBy},getdate())")
    @Options(useGeneratedKeys=true,keyProperty="id",keyColumn="id")
    void insert(Goods goods);
    
    @Delete(value="delete from TA_TestGoods where id=#{id}")
    void delete(int id);
    
    @Update("update TA_TestGoods set title=#{title},picture=#{picture},price=#{price},introduction=#{introduction}," + 
            "categoryId=#{categoryId},lastEditBy=#{lastEditBy},lastEditTime=getdate()  " + 
            "where id=#{id}")
    void update(Goods goods);
    
    
}

 如上代碼重點說明:

a.增刪改查,對應的註解是:insert、delete、update、select;

b.SQL註解中的參數佔位符有兩種,一種是:#{xxx},最後會生成?的參數化執行,另外一種是:${xxx} 則最後會直接替換成參數的值,即拼SQL(除非信任參數或一些時間、數字類型,不然不建議這種,存在SQL注入風險);

c.insert時若是有自增ID,則能夠經過添加Options註解,並指定useGeneratedKeys=true,keyProperty="數據實體類的屬性字段名",keyColumn="表自增ID的字段名",這樣當insert成功後會自動回填到數據實體類的自增ID對應的屬性上;

d.若是想要生成in子句查詢,則如上代碼getListByMultIds方法上的select註解中使用<script>xxx<foreach>xx</foreach>xx</script>格式實現,若是想用實現複雜的一對一,一對多,多對多等複雜的查詢,則須要添加results註解並指定相應的關聯關係,同時select SQL語句也應關聯查詢,可參見:https://blog.csdn.net/desert568/article/details/79079151

以上2步即完成一個mapper操做類;

  2.2全手寫AVA代碼+Mapper XML實現Mybatis的CRUD;

  2.2.1.仍然是在cn.zuowenjun.boot.domain包中定義一個數據實體模型類(ShoppingCart:購物車信息),代碼以下:【注意這裏有一個關聯商品信息的屬性:inGoods】

package cn.zuowenjun.boot.domain;

import java.util.Date;

public class ShoppingCart {
    private int id;
    private String shopper;
    private int goodsId;
    private int qty;
    private Date addedTime;
    private Goods inGoods;
    
    public ShoppingCart() {
        
    }
    
    public ShoppingCart(int id,String shopper,int goodsId,int qty,Date addedTime) {
        this.id=id;
        this.shopper=shopper;
        this.goodsId=goodsId;
        this.qty=qty;
        this.addedTime=addedTime;
    }
    
    
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    
    public String getShopper() {
        return shopper;
    }
    public void setShopper(String shopper) {
        this.shopper = shopper;
    }
    
    public int getGoodsId() {
        return goodsId;
    }
    public void setGoodsId(int goodsId) {
        this.goodsId = goodsId;
    }
    
    public int getQty() {
        return qty;
    }
    public void setQty(int qty) {
        this.qty = qty;
    }
    
    public Date getAddedTime() {
        return addedTime;
    }
    public void setAddedTime(Date addedTime) {
        this.addedTime = addedTime;
    }

    public Goods getInGoods() {
        return inGoods;
    }

    public void setInGoods(Goods inGoods) {
        this.inGoods = inGoods;
    }

    

}
View Code

  2.2.2.仍然是在cn.zuowenjun.boot.mapper包中定義數據操做接口(interface),注意這裏只是定義接口,並不包含SQL註解部份,由於這部份將在Mapper的XML代碼中進行配置實現,代碼以下:

package cn.zuowenjun.boot.mapper;

import java.util.List;

import org.apache.ibatis.annotations.Param;

import cn.zuowenjun.boot.domain.*;

public interface ShoppingCartMapper {
    
    List<ShoppingCart> getList(String shopper);
    
    void insert(ShoppingCart shoppingCart);
    
    void update(ShoppingCart shoppingCart);
    
    void deleteItem(int id);
    
    void delete(String shopper);
    
    int getBuyCount(String shopper);
    
    ShoppingCart get(@Param("shopper") String shopper,@Param("goodsId") int goodsId); 
}

如上代碼有一個重點說明:get方法有兩個參數(多個參數也相似),爲了mybatis可以自動映射到這些參數,必需爲每一個參數添加Param註解,並指定參數名,這個參數名是與對應的Mapper XML中的SQL語句中定義的參數名相同。

  2.2.3.在mybatis.mapper-locations設置的mapper xml存放的路徑中建立XML文件,並手動編寫映射的SQL語句,以下所示:

<?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="cn.zuowenjun.boot.mapper.ShoppingCartMapper">
    <resultMap id="shoppingCartMap" type="ShoppingCart" >
        <id column="id" property="id" jdbcType="INTEGER" />
        <result column="shopper" property="shopper" jdbcType="NVARCHAR" />
        <result column="goodsId" property="goodsId" jdbcType="INTEGER" />
        <result column="qty" property="qty" jdbcType="INTEGER"/>
        <result column="addedTime" property="addedTime" jdbcType="DATE" />
        <!-- referseee https://www.cnblogs.com/ysocean/p/7237499.html -->
        <association property="inGoods" javaType="cn.zuowenjun.boot.domain.Goods">
            <id column="id" property="id" jdbcType="INTEGER" />
            <result column="title" property="title" />
            <result column="picture" property="picture" />
            <result column="price" property="price" />
            <result column="introduction" property="introduction" />
            <result column="categoryId" property="categoryId" />
            <result column="lastEditBy" property="lastEditBy" />
            <result column="lastEditTime" property="lastEditTime" />
        </association>
    </resultMap>

    <!-- 若是返回的結果與某個實體類徹底相同,其實徹底不須要上面的resultMap,而是直接使用resultType=類名,
        如:resultType=cn.zuowenjun.boot.domain.ShoppingCart(簡寫別名:ShoppingCart),此處是示例用法,故採起指定映射 -->
    <select id="getList" parameterType="string" resultMap="shoppingCartMap">
        select * from TA_TestShoppingCart a inner join TA_TestGoods b  on a.goodsId=b.id  
        where shopper=#{shopper} order by addedTime
    </select>
    
    <select id="getBuyCount" parameterType="string" resultType="int">
        select count(1) from (select goodsId from TA_TestShoppingCart where shopper=#{shopper} 
        group by goodsId) as t
    </select>
    
    <select id="get"  resultMap="shoppingCartMap">
         select * from TA_TestShoppingCart a inner join TA_TestGoods b  on a.goodsId=b.id  
         where shopper=#{shopper} and goodsId=#{goodsId}
    </select>
    
    <insert id="insert" parameterType="ShoppingCart" 
        useGeneratedKeys="true" keyProperty="id" keyColumn="id">
        insert into TA_TestShoppingCart(shopper, goodsId, qty, addedTime) 
        values(#{shopper},#{goodsId},#{qty},getdate())
    </insert>
    
    <update id="update" parameterType="ShoppingCart" >
        update TA_TestShoppingCart set shopper=#{shopper},goodsId=#{goodsId},qty=#{qty},addedTime=getdate() 
        where id=#{id}
    </update>
    
    <delete id="deleteItem" parameterType="int">
        delete from TA_TestShoppingCart where id=#{id}
    </delete>
    
    <delete id="delete" parameterType="string">
        delete from TA_TestShoppingCart where shopper=#{shopper}
    </delete>
    
    
</mapper>

如上XML重點說明:

a.凡是使用到類型的地方,能夠在mybatis-config.xml中提早配置類型別名,以簡化配置,固然mybatis已默認設置了一些別名以減小你們配置的工做量,如:string,對應的類型是String等,詳見:http://www.mybatis.org/mybatis-3/zh/configuration.html#typeAliases

b.因爲這個ShoppingCart有關聯屬性:inGoods,故在查詢時都會關聯查詢goods表並經過在resultMap中經過association 元素來指定關聯關係,更多複雜的XML配置詳見:http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html

以上3步即完成一個mapper操做類,相比直接使用mapper接口+SQL註解多了一個步驟,但這樣的好處是因爲沒有寫死在代碼中,能夠很容易的更改mapper的相關SQL語句,減小代碼改動量。

  2.3使用Mybatis Generator的Maven插件自動生成Mybatis的CRUD;

   經過上面的介紹,咱們知道有2種方法來實現一個mapper數據操做類(dao),顯然第2種更能適應更改的狀況,但因爲手寫mapper xml文件很是的麻煩,故能夠經過Mybatis Generator組件,自動生成相關的代碼及xml(通常是:數據實體類domain、數據處理接口mapper、mapper XML),具體實現步驟以下:(能夠單獨一個項目來生成這些文件,也能夠集成在一個項目中,因爲是演示,我這裏是集成在一個項目中)

  2.3.1.因爲要使用Mybatis Generator組件,故須要添加對應的JAR包依賴,以下所示:

        <!--SQL SERVER 數據驅動,以提供數據訪問支持-->
<dependency>
            <groupId>com.microsoft.sqlserver</groupId>
            <artifactId>mssql-jdbc</artifactId>
            <version>7.0.0.jre8</version><!--$NO-MVN-MAN-VER$ -->
        </dependency>


        <!-- 添加mybatis生成器,以便經過maven build自動生成model、mapper及XML -->
        <dependency>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-core</artifactId>
            <version>1.3.7</version>
        </dependency>

同時須要添加對應的maven插件,以便經過maven命令可執行生成過程,以下:(經過configurationFile元素指定生成器的配置路徑,overwrite元素指定是否覆蓋生成,這裏有個坑,後面會介紹到,此處略)

<build>
        <plugins>            
      <plugin>
                <!--ref: https://gitee.com/free/Mybatis_Utils/blob/master/MybatisGeneator/MybatisGeneator.md -->
                <!--ref: https://www.cnblogs.com/handsomeye/p/6268513.html -->
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.3.7</version>
                <configuration>
                    <configurationFile>src/main/resources/mybatis/generatorconfig.xml</configurationFile>
                    <verbose>true</verbose>
                    <overwrite>true</overwrite>
                </configuration>
            </plugin>
        </plugins>
  </build>

 2.3.2.在cn.zuowenjun.boot.domain包中定義相關的數據實體模型類,我這裏演示的類是:ShoppingOrder(購物訂單信息),代碼以下:

package cn.zuowenjun.boot.domain;

import java.math.BigDecimal;
import java.util.Date;

public class ShoppingOrder {
    private Integer id;

    private String shopper;

    private Integer totalqty;

    private BigDecimal totalprice;

    private Boolean iscompleted;

    private String createby;

    private Date createtime;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getShopper() {
        return shopper;
    }

    public void setShopper(String shopper) {
        this.shopper = shopper == null ? null : shopper.trim();
    }

    public Integer getTotalqty() {
        return totalqty;
    }

    public void setTotalqty(Integer totalqty) {
        this.totalqty = totalqty;
    }

    public BigDecimal getTotalprice() {
        return totalprice;
    }

    public void setTotalprice(BigDecimal totalprice) {
        this.totalprice = totalprice;
    }

    public Boolean getIscompleted() {
        return iscompleted;
    }

    public void setIscompleted(Boolean iscompleted) {
        this.iscompleted = iscompleted;
    }

    public String getCreateby() {
        return createby;
    }

    public void setCreateby(String createby) {
        this.createby = createby == null ? null : createby.trim();
    }

    public Date getCreatetime() {
        return createtime;
    }

    public void setCreatetime(Date createtime) {
        this.createtime = createtime;
    }
}
View Code

  2.3.3.配置generatorconfig.xml,指定生成的各個細節,因爲generatorconfig的配置節點比較多,以下只是貼出當前示例的配置信息,有一點要說明,注意配置節點的順序,若是順序不對就會報錯,完整的配置方法詳情介紹可參見:https://gitee.com/free/Mybatis_Utils/blob/master/MybatisGeneator/MybatisGeneator.md 或 http://www.javashuo.com/article/p-mvxaarni-co.html

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <properties resource="application.properties" />
    <!-- https://blog.csdn.net/zsy3313422/article/details/53190613 -->
    <classPathEntry
        location="E:/LocalMvnRepositories/com/microsoft/sqlserver/mssql-jdbc/7.0.0.jre8/mssql-jdbc-7.0.0.jre8.jar" />
    <context id="my" targetRuntime="MyBatis3Simple" defaultModelType="flat">
        <property name="javaFileEncoding" value="UTF-8" />

        
        <commentGenerator>
            <property name="suppressAllComments" value="true" />
            <property name="suppressDate" value="true" />
        </commentGenerator>
        <jdbcConnection
            driverClass="${spring.datasource.driverClassName}"
            connectionURL="${spring.datasource.url}"
            userId="${spring.datasource.username}"
            password="${spring.datasource.password}">
        </jdbcConnection>

        <!-- 生成model實體類文件位置 -->
        <javaModelGenerator
            targetPackage="cn.zuowenjun.boot.domain"
            targetProject="src/main/java">
            <property name="enableSubPackages" value="false" />
            <property name="trimStrings" value="true" />
        </javaModelGenerator>

        <!-- 生成mapper.xml配置文件位置 -->
        <!-- targetPackage這裏指定包名,則會在以下的路徑中生成多層級目錄 -->
        <sqlMapGenerator targetPackage="mybatis.mapper"
            targetProject="src/main/resources">
            <property name="enableSubPackages" value="false" />
        </sqlMapGenerator>

        <!-- 生成mapper接口文件位置 -->
        <javaClientGenerator
            targetPackage="cn.zuowenjun.boot.mapper"
            targetProject="src/main/java" type="XMLMAPPER">
            <property name="enableSubPackages" value="false" />
        </javaClientGenerator>

        <table tableName="TA_TestShoppingOrder"
            domainObjectName="ShoppingOrder">
            <generatedKey column="id" sqlStatement="JDBC"  identity="true" /><!-- 指示ID爲自增ID列,並在插入後返回該ID -->
        </table>



    </context>
</generatorConfiguration> 
View Code

因爲涉及的知識點比較多,在此就不做介紹,請參見我給出的連接加以瞭解。

  2.3.4.經過maven 插件來執行生成代碼(生成代碼有不少種方法,詳見:http://www.javashuo.com/article/p-gkvefehv-bk.html),這裏我使用最爲方便的一種,步驟以下:

  項目右鍵-》RunAs或者DeBug-》Maven Build...-》在goals(階段)中輸入:mybatis-generator:generate,即:設置生成階段,最後點擊Apply或直接Run便可,如圖示:

  

  執行生成後,會在控制檯中顯示最終的結果,以下圖示:若是成功會顯示buid success,並會在相應的目錄中生成對應的文件

  2.4進階用法:自定義Mybatis Generator的生成過程當中的插件類,以便添加額外自定義的方法

  雖然使用Mybatis Generator減小了手工編寫代碼及XML的工做量,但因爲生成的CRUD方法都是比較簡單的,稍微複雜或靈活一點的方法都不能簡單生成,若是單純的在生成代碼後再人工手動添加其它自定義的方法,又擔憂若是執行一次自動生成又會覆蓋手動添加的自定義代碼,那有沒有辦法解決呢?固然是有的,我(夢在旅途,zuowenjun.cn)在網絡上了解到的方法大部份都是說獲取Mybatis Generator源代碼,而後進行二次開發,最後使用「定製版」的Mybatis Generator,我我的以爲雖然能解決問題,但若是能力不足,可能會出現意想不到的問題,並且進行定製也不是那麼簡單的,故我這裏採起Mybatis Generator框架提供的可擴展插件plugin來實現擴展,具體步驟以下:

  2.4.1.在項目新建立一個包cn.zuowenjun.boot.mybatis.plugin,而後在包裏面先建立一個泛型通用插件基類(CustomAppendMethodPlugin),這個基類主要是用於附加自定義方法,故取名CustomAppendMethodPlugin,代碼以下:

package cn.zuowenjun.boot.mybatis.plugin;

import java.util.List;

import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.PluginAdapter;
import org.mybatis.generator.api.dom.java.Interface;
import org.mybatis.generator.api.dom.java.TopLevelClass;
import org.mybatis.generator.api.dom.xml.Document;
import org.mybatis.generator.codegen.mybatis3.javamapper.elements.AbstractJavaMapperMethodGenerator;
import org.mybatis.generator.codegen.mybatis3.xmlmapper.elements.AbstractXmlElementGenerator;

/*
 * 自定義通用可添加生成自定義方法插件類
 * Author:zuowenjun
 * Date:2019-1-29
 */
public abstract class CustomAppendMethodPlugin<TE extends AbstractXmlElementGenerator,TM extends AbstractJavaMapperMethodGenerator>  
extends PluginAdapter {

    protected final Class<TE> teClass;
    protected final Class<TM> tmClass;
    
    
    @SuppressWarnings("unchecked")
    public CustomAppendMethodPlugin(Class<? extends AbstractXmlElementGenerator> teClass,
            Class<? extends AbstractJavaMapperMethodGenerator> tmClass) {
        this.teClass=(Class<TE>) teClass;
        this.tmClass=(Class<TM>) tmClass;
    }
    
    @Override
    public boolean sqlMapDocumentGenerated(Document document,
            IntrospectedTable introspectedTable) {
            
            try {
                AbstractXmlElementGenerator elementGenerator = teClass.newInstance();
                elementGenerator.setContext(context);
                elementGenerator.setIntrospectedTable(introspectedTable);
                elementGenerator.addElements(document.getRootElement());
                
            } catch (InstantiationException | IllegalAccessException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            

        return super.sqlMapDocumentGenerated(document, introspectedTable);
    }
    
    @Override
    public boolean clientGenerated(Interface interfaze,
            TopLevelClass topLevelClass,
            IntrospectedTable introspectedTable) {
        
        try {
            AbstractJavaMapperMethodGenerator methodGenerator = tmClass.newInstance();
            methodGenerator.setContext(context);
            methodGenerator.setIntrospectedTable(introspectedTable);
            methodGenerator.addInterfaceElements(interfaze);
            
        } catch (InstantiationException | IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        

        return super.clientGenerated(interfaze, topLevelClass, introspectedTable);
    }

    @Override
    public boolean validate(List<String> warnings) {
        // TODO Auto-generated method stub
        return true;
    }

}
View Code

  代碼比較簡單,主要是重寫了sqlMapDocumentGenerated(生成mapper xml方法)、clientGenerated(生成mapper 接口方法),在這裏面我經過把指定泛型類型(分別繼承自 AbstractXmlElementGenerator、AbstractJavaMapperMethodGenerator)加入到生成XML和接口的過程當中,以實現生成過程的抽象。

  2.4.2.我這裏因爲默認生成的ShoppingOrderDetailMapper(實體類:ShoppingOrderDetail是購物訂單詳情)沒法知足須要,我須要額外再增長兩個方法:

  List<ShoppingOrderDetail> selectByOrderId(int shoppingOrderId); 、void deleteByOrderId(int shoppingOrderId); 故在這裏自定義繼承自CustomAppendMethodPlugin的插件類:ShoppingOrderDetailMapperPlugin,具體實現代碼以下:

package cn.zuowenjun.boot.mybatis.plugin;

import java.util.Set;
import java.util.TreeSet;

import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
import org.mybatis.generator.api.dom.java.Interface;
import org.mybatis.generator.api.dom.java.JavaVisibility;
import org.mybatis.generator.api.dom.java.Method;
import org.mybatis.generator.api.dom.java.Parameter;
import org.mybatis.generator.api.dom.xml.Attribute;
import org.mybatis.generator.api.dom.xml.TextElement;
import org.mybatis.generator.api.dom.xml.XmlElement;
import org.mybatis.generator.codegen.mybatis3.javamapper.elements.AbstractJavaMapperMethodGenerator;
import org.mybatis.generator.codegen.mybatis3.xmlmapper.elements.AbstractXmlElementGenerator;

/*
 * ref see https://www.cnblogs.com/se7end/p/9293755.html
 * Author:zuowenjun
 * Date:2019-1-29
 */
public class ShoppingOrderDetailMapperPlugin 
extends CustomAppendMethodPlugin<ShoppingOrderDetailXmlElementGenerator, AbstractJavaMapperMethodGenerator> {

    public ShoppingOrderDetailMapperPlugin() {
        super(ShoppingOrderDetailXmlElementGenerator.class,ShoppingOrderDetailJavaMapperMethodGenerator.class);
    }

}


class ShoppingOrderDetailXmlElementGenerator extends AbstractXmlElementGenerator{

    @Override
    public void addElements(XmlElement parentElement) {
        
        if(!introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime().equalsIgnoreCase("TA_TestShoppingOrderDetail")) {
            return;
        }

        TextElement selectText = new TextElement("select * from " + introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime()
        + " where shoppingOrderId=#{shoppingOrderId}");
        
         XmlElement selectByOrderId = new XmlElement("select");
         selectByOrderId.addAttribute(new Attribute("id", "selectByOrderId"));
         selectByOrderId.addAttribute(new Attribute("resultMap", "BaseResultMap"));
         selectByOrderId.addAttribute(new Attribute("parameterType", "int"));
         selectByOrderId.addElement(selectText);
         parentElement.addElement(selectByOrderId);
         
            TextElement deleteText = new TextElement("delete from " + introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime()
            + " where shoppingOrderId=#{shoppingOrderId}");
            
             XmlElement deleteByOrderId = new XmlElement("delete");
             deleteByOrderId.addAttribute(new Attribute("id", "deleteByOrderId"));
             deleteByOrderId.addAttribute(new Attribute("parameterType", "int"));
             deleteByOrderId.addElement(deleteText);
             parentElement.addElement(deleteByOrderId);
    }
    
}


class ShoppingOrderDetailJavaMapperMethodGenerator extends AbstractJavaMapperMethodGenerator{

    @Override
    public void addInterfaceElements(Interface interfaze) {
        
        if(!introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime().equalsIgnoreCase("TA_TestShoppingOrderDetail")) {
            return;
        }
        
        addInterfaceSelectByOrderId(interfaze);
        addInterfaceDeleteByOrderId(interfaze);
    }
    
       private void addInterfaceSelectByOrderId(Interface interfaze) {
            // 先建立import對象
            Set<FullyQualifiedJavaType> importedTypes = new TreeSet<FullyQualifiedJavaType>();
            // 添加Lsit的包
            importedTypes.add(FullyQualifiedJavaType.getNewListInstance());
            // 建立方法對象
            Method method = new Method();
            // 設置該方法爲public
            method.setVisibility(JavaVisibility.PUBLIC);
            // 設置返回類型是List
            FullyQualifiedJavaType returnType = FullyQualifiedJavaType.getNewListInstance();
            FullyQualifiedJavaType listArgType = new FullyQualifiedJavaType(introspectedTable.getBaseRecordType());
            returnType.addTypeArgument(listArgType);
            
            // 方法對象設置返回類型對象
            method.setReturnType(returnType);
            // 設置方法名稱爲咱們在IntrospectedTable類中初始化的 「selectByOrderId」
            method.setName("selectByOrderId");

            // 設置參數類型是int類型
            FullyQualifiedJavaType parameterType;
            parameterType = FullyQualifiedJavaType.getIntInstance();
            // import參數類型對象(基本類型其實能夠沒必要引入包名)
            //importedTypes.add(parameterType);
            // 爲方法添加參數,變量名稱record
            method.addParameter(new Parameter(parameterType, "shoppingOrderId")); //$NON-NLS-1$
            //
            context.getCommentGenerator().addGeneralMethodComment(method, introspectedTable);
            if (context.getPlugins().clientSelectByPrimaryKeyMethodGenerated(method, interfaze, introspectedTable)) {
                interfaze.addImportedTypes(importedTypes);
                interfaze.addMethod(method);
            }
        }
       
       private void addInterfaceDeleteByOrderId(Interface interfaze) {
            // 建立方法對象
            Method method = new Method();
            // 設置該方法爲public
            method.setVisibility(JavaVisibility.PUBLIC);
            // 設置方法名稱爲咱們在IntrospectedTable類中初始化的 「deleteByOrderId」
            method.setName("deleteByOrderId");
            // 設置參數類型是int類型
            FullyQualifiedJavaType parameterType;
            parameterType = FullyQualifiedJavaType.getIntInstance();
            method.addParameter(new Parameter(parameterType, "shoppingOrderId")); //$NON-NLS-1$
            
            context.getCommentGenerator().addGeneralMethodComment(method, introspectedTable);
            if (context.getPlugins().clientSelectByPrimaryKeyMethodGenerated(method, interfaze, introspectedTable)) {
                interfaze.addMethod(method);
            }
            
       }
        
    
}

從如上代碼所示,核心點是自定義繼承自AbstractXmlElementGenerator、AbstractJavaMapperMethodGenerator的ShoppingOrderDetailXmlElementGenerator(XML生成器類)、ShoppingOrderDetailJavaMapperMethodGenerator(mapper接口生成器類),而後分別在addElements、addInterfaceElements添加自定義生成XML及接口方法的邏輯(如上代碼中使用的是反射,若想學習瞭解反射請自行網上查找相關資料,C#也有反射哦,應該好理解),注意因爲插件在生成過程當中每一個實體類都會調用一次,故必需做相應的判斷(判斷當前要附加的自定義方法是符與當前實體類生成過程相符,若是不相符則忽略退出)

以下是ShoppingOrderDetail實體類的代碼:

package cn.zuowenjun.boot.domain;

import java.math.BigDecimal;
import java.util.Date;

public class ShoppingOrderDetail {
    private Integer id;

    private Integer shoppingorderid;

    private Integer goodsid;

    private Integer qty;

    private BigDecimal totalprice;

    private String createby;

    private Date createtime;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getShoppingorderid() {
        return shoppingorderid;
    }

    public void setShoppingorderid(Integer shoppingorderid) {
        this.shoppingorderid = shoppingorderid;
    }

    public Integer getGoodsid() {
        return goodsid;
    }

    public void setGoodsid(Integer goodsid) {
        this.goodsid = goodsid;
    }

    public Integer getQty() {
        return qty;
    }

    public void setQty(Integer qty) {
        this.qty = qty;
    }

    public BigDecimal getTotalprice() {
        return totalprice;
    }

    public void setTotalprice(BigDecimal totalprice) {
        this.totalprice = totalprice;
    }

    public String getCreateby() {
        return createby;
    }

    public void setCreateby(String createby) {
        this.createby = createby == null ? null : createby.trim();
    }

    public Date getCreatetime() {
        return createtime;
    }

    public void setCreatetime(Date createtime) {
        this.createtime = createtime;
    }
}
View Code

另外順便解決一個踩坑點:上面提到了,咱們在POM文件配置mybatis-generator-maven-plugin插件時,overwrite設爲true,目的是確保每次執行生成時,生成的代碼可以覆蓋已經存在的,理想是美好的,但現實總會有點小意外,咱們這樣配置,只能解決生成的mapper 接口類文件不會重複,但生成的mapper xml文件仍然會附加代碼致使重複,故咱們須要解決這個問題,而解決這個問題的關鍵是:GeneratedXmlFile.isMergeable,若是isMergeable爲true則會合並,目前默認都是false,因此咱們只需實現將GeneratedXmlFile.isMergeable設爲true便可,因爲isMergeable是私有字段,只能採起插件+反射動態改變這個值了,自定義合併代碼插件OverIsMergeablePlugin實現以下:

package cn.zuowenjun.boot.mybatis.plugin;

import java.lang.reflect.Field;
import java.util.List;

import org.mybatis.generator.api.GeneratedXmlFile;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.PluginAdapter;

/*
 * 修復mybatis-generator重複執行時生成的XML有重複代碼(核心:isMergeable=false)
 * Author:https://blog.csdn.net/zengqiang1/article/details/79381418
 * Editor:zuowenjun
 */
public class OverIsMergeablePlugin extends PluginAdapter {

    @Override
    public boolean validate(List<String> warnings) {
        return true;
    }
    

    @Override
    public boolean sqlMapGenerated(GeneratedXmlFile sqlMap,
            IntrospectedTable introspectedTable) {
        
          try {
              Field field = sqlMap.getClass().getDeclaredField("isMergeable");
              field.setAccessible(true);
              field.setBoolean(sqlMap, false);
          } catch (Exception e) {
              e.printStackTrace();
          }
        
        return true;
    }

}

2.4.3.在generatorconfig.xml配置文件中增長plugin配置,以下:

        <plugin type="cn.zuowenjun.boot.mybatis.plugin.OverIsMergeablePlugin"></plugin>
        <plugin type="cn.zuowenjun.boot.mybatis.plugin.ShoppingOrderDetailMapperPlugin"></plugin>

... ...省略中間過程

        <table tableName="TA_TestShoppingOrderDetail"
            domainObjectName="ShoppingOrderDetail">
            <generatedKey column="id" sqlStatement="JDBC" identity="true" />
        </table>

2.4.4.因爲不能在同一個項目中直接使用plugin類(具體緣由請上網查詢,在此瞭解便可),故還需把cn.zuowenjun.boot.mybatis.plugin這個包中的文件單獨導出生成JAR包,而後把這個JAR包複製到項目的指定目錄下(本示例是放在libs目錄下),而後再在POM爲mybatis-generator-maven-plugin單獨添加system本地依賴才行,maven添加依賴以下:

            <plugin>
                <!--ref: https://gitee.com/free/Mybatis_Utils/blob/master/MybatisGeneator/MybatisGeneator.md -->
                <!--ref: https://www.cnblogs.com/handsomeye/p/6268513.html -->
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.3.7</version>
                <configuration>
                    <configurationFile>src/main/resources/mybatis/generatorconfig.xml</configurationFile>
                    <verbose>true</verbose>
                    <overwrite>true</overwrite>
                </configuration>
                <dependencies>
                    <!-- 爲mybatis-generator增長自定義插件依賴 -->
                    <dependency>
                        <groupId>cn.zuowenjun.boot.mybatis.plugin</groupId>
                        <artifactId>cn.zuowenjun.boot.mybatis.plugin</artifactId>
                        <version>1.0</version>
                        <scope>system</scope>
                        <systemPath>${basedir}/src/main/libs/cn.zuowenjun.boot.mybatis.plugin.jar</systemPath>
                    </dependency>
                </dependencies>
            </plugin>

若是4步完成後,最後執行maven buid的生成mybatis代碼過程便可,最後查看生成的mapper及xml都會有對應的自定義方法,在此就再也不貼出結果了。

  2.5進階用法:利用Mybatis的繼承機制實現添加額外自定義方法

  如2.4節所述,咱們能夠經過自定義plugin來實現添加額外自定義的方法,並且不用擔憂被覆蓋,但可能實現有點麻煩(裏面用到了反射),有沒有簡單一點的辦法呢?固然有,便可以先使用Mybatis Generator框架生成默代代碼,而後再結合使用2.2所述方法(手寫mapper接口類及mapper XML),利用mapper XML的繼承特性完成添加自定義方法的過程當中,具體步驟與2.2相同,在此貼出(注意前提是先自動生成代碼,而後再操做以下步驟)

  2.5.1.定義擴展mapper接口類(ShoppingOrderExtMapper,擴展ShoppingOrderMapper,它們之間無需繼承),代碼以下:(很簡單,就是定義了一個特殊用途的方法)

package cn.zuowenjun.boot.mapper;

import java.util.List;

import cn.zuowenjun.boot.domain.ShoppingOrder;

public interface ShoppingOrderExtMapper {
    
    List<ShoppingOrder> selectAllByShopper(String shopper);
}

  2.5.2.編寫對應的ShoppingOrderExtMapper.xml,這裏面就要用到繼承,繼承主要是resultMap【實現繼承用:extends=要繼承的mapper xml resultMap】,這樣就不用兩個地方都爲一個實體類寫結果映射配置了,其他的都按一個新的mapper 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="cn.zuowenjun.boot.mapper.ShoppingOrderExtMapper">
    <resultMap id="BaseResultMap" type="cn.zuowenjun.boot.domain.ShoppingOrder" 
    extends="cn.zuowenjun.boot.mapper.ShoppingOrderMapper.BaseResultMap">
    </resultMap>
    <select id="selectAllByShopper" resultMap="BaseResultMap" parameterType="string">
        select * from TA_TestShoppingOrder where shopper=#{shopper}
    </select>
</mapper>

如上兩步即完成擴展添加額外自定義的方法,又不用擔憂重複執行生成代碼會被覆蓋掉,只是使用時須要單獨註冊到spring,單獨實例,雖不完美但彌補了默認生成代碼的不足也是可行的。

  2.6 使用SpringBootTest + junit測試基於Mybatis框架實現的DAO類

   在此不詳情說明junit測試的用法,網上大把資源,只是單獨說明結合SpringBootTest 註解,完成單元測試,先看單元測試代碼:

package cn.zuowenjun.springbootdemo;


import java.math.BigDecimal;
import java.util.Date;
import java.util.List;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;

import cn.zuowenjun.boot.SpringbootdemoApplication;
import cn.zuowenjun.boot.domain.*;
import cn.zuowenjun.boot.mapper.GoodsMapper;
import cn.zuowenjun.boot.mapper.ShoppingOrderDetailMapper;
import cn.zuowenjun.boot.mapper.ShoppingOrderMapper;

@RunWith(SpringRunner.class)
@SpringBootTest(classes=SpringbootdemoApplication.class)
public class ShoppingOrderMapperTests {

    @Autowired
    private ShoppingOrderMapper shoppingOrderMapper;
    
    @Autowired
    private ShoppingOrderDetailMapper shoppingOrderDetailMapper;
    
    @Autowired
    private GoodsMapper goodsMapper;
    
    @Transactional
    @Rollback(false) //不加這個,默認測試完後自動回滾
    @Test
    public void testInsertShoppingOrder() {

        Goods goods= goodsMapper.get(1);
        
        ShoppingOrder shoppingOrder=new ShoppingOrder();
        shoppingOrder.setShopper("zuowenjun");
        shoppingOrder.setIscompleted(false);
        shoppingOrder.setTotalprice(BigDecimal.valueOf(0));
        shoppingOrder.setTotalqty(1);
        shoppingOrder.setCreateby("zuowenjun");
        shoppingOrder.setCreatetime(new Date());
        
        int orderId= shoppingOrderMapper.insert(shoppingOrder);
        shoppingOrder.setId(orderId);
        
        ShoppingOrderDetail shoppingOrderDetail=new ShoppingOrderDetail();
        shoppingOrderDetail.setGoodsid(goods.getId());
        shoppingOrderDetail.setShoppingorderid(shoppingOrder.getId());
        shoppingOrderDetail.setQty(10);
        shoppingOrderDetail.setTotalprice(BigDecimal.valueOf(shoppingOrderDetail.getQty()).multiply(goods.getPrice()));
        shoppingOrderDetail.setCreateby("zuowenjun");
        shoppingOrderDetail.setCreatetime(new Date());
        
        shoppingOrderDetailMapper.insert(shoppingOrderDetail);
        
        List<ShoppingOrderDetail> orderDetails= shoppingOrderDetailMapper.selectByOrderId(shoppingOrder.getId());
        if(orderDetails!=null && orderDetails.size()>0) {
            for(ShoppingOrderDetail od:orderDetails) {
                System.out.println("id:" + od.getId() + ",goodsid:" + od.getGoodsid());
            }
        }
        
        Assert.assertTrue(orderDetails.size()>0); 
    }
}
View Code

  與Junit單元測試用法基本相同,惟 一的區別就是在單元測試的類上添加@SpringBootTest,並指定啓動類(如代碼中所示:@SpringBootTest(classes=SpringbootdemoApplication.class)),另外注意一點:若是測試方法使用@Transactional註解,那麼當測試完成後會回滾(即並不會提交事務),若是想完成事務的提交,則需如代碼中所示添加@Rollback(false),其中false指不回滾,true則爲回滾。

3、簡單演示集成Thymeleaf模板引擎(這裏只是用一個簡單的頁面演示效果,因爲如今都流行先後端分離,故只需瞭解便可)

  說明:Thymeleaf是spring MVC的端視圖引擎,與JSP視圖引擎相似,只不過在spring boot項目中默認支持Thymeleaf(Thymeleaf最大的優勢是視圖中不含JAVA代碼,不影響UI美工及前端設計),而JSP不建議使用,固然也能夠經過添加相關的JSP的JAR包依賴,實現JSP視圖,具體請自行網上查找資源,同時spring MVC +JSP視圖的用法能夠參見該系列的上篇文章

  3.1.添加Thymeleaf的maven依賴,POM配置以下:

        <!-- 添加thymeleaf模板引擎(用於springMVC模式,若是是rest API項目,則無需引用) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

  3.2.編寫後端controller,以便響應用戶請求,代碼以下:(這個與普通spring MVC+JSP相同,區別在VIEW)

package cn.zuowenjun.boot.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import cn.zuowenjun.boot.domain.*;
import cn.zuowenjun.boot.service.*;

@Controller
@RequestMapping("/test")
public class TestController {
    
    @Autowired
    private ShopUserService shopUserService;
    
    @GetMapping("/userlist")
    public String list(Model model) {
        
        List<ShopUser> users= shopUserService.getAll();
        model.addAttribute("title", "測試使用thymeleaf模板引擎展現數據");
        model.addAttribute("users", users);
        
        //能夠在application.properties添加以下配置,以改變thymeleaf的默認設置
        //spring.thymeleaf.prefix="classpath:/templates/" 模板查找路徑
        //spring.thymeleaf.suffix=".html" 模板後綴名
        
        return "/test";//默認自動查找路徑:src/main/resources/templates/*.html
    }
}

  3.3編寫前端視圖html模板頁面,最後演示效果

   HTML視圖頁面代碼:(th:XXX爲Thymeleaf的模板特有的標識符,${xxx}這是SP EL表達式,這個以前講過的,很簡單,不展開說明)

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>test User List -power by thymeleaf</title>
<style type="text/css">
    table{
        border:2px solid blue;
        border-collapse:collapse;
        width:98%;
    }
    
    table *{
        border:1px solid blue;
        text-align:center;
    }
    
    thead{
        background-color:purple;
        color:yellow;
    }
    
    th,td{
        padding:5px;
    }
    
    #copy{
        margin-top:100px;
        text-align: center;
    }
    
</style>
</head>
<body>
    <h1 th:text="${title}"></h1>
    <table>
        <thead>
            <tr>
                <th>SeqNo</th>
                <th>userId</th>
                <th>nickName</th>
                <th>depositAmount</th>
            </tr>
        </thead>
        <tbody>
            <tr th:if="${users}!=null" th:each="user,iterStat:${users}">
                <td th:text="${iterStat.index}+1">1</td>
                <td th:text="${user.userid}">www.zuowenjun.cn</td>
                <td th:text="${user.nickname}">夢在旅途</td>
                <td th:text="${user.depositamount}">520</td>
            </tr>
            <tr th:unless="${users.size()} gt 0">
                <td colspan="4">暫無相關記錄!</td>
            </tr>
        </tbody>
    </table>
    <p id="copy">
    Copyright &copy;<span th:text="${#dates.format(#dates.createToday(),'yyyy')}"></span>
    www.zuowenjun.cn and zuowj.cnblogs.com demo all rights.
    </p>
</body>
</html>
View Code

最後瀏覽:http://localhost:8080/test/userlist,效果以下圖示:

4、利用VUE+SpringMVC Rest API編寫實現先後端分離的電商購物Demo(瀏覽商品、添加購物車、下單、完成)

說明:因爲數據訪問層(或稱:數據持久層)已由Mybatis Generator完成了,如今就只要編寫業務領域服務層(接口層、實現層),API接入層便可完成後端開發,而後再開發設計前端頁面便可(前端與後端交互使用AJAX)

  4.1.在cn.zuowenjun.boot.service包中定義相關的業務領域服務接口

//ShopUserService.java

package cn.zuowenjun.boot.service;

import java.util.List;

import cn.zuowenjun.boot.domain.ShopUser;

public interface ShopUserService {

    List<ShopUser> getAll();
    
    ShopUser get(String userId);
    
    String getCurrentLoginUser();
    
    String login(String uid,String pwd);
    
    void logout();
}

//GoodsService.java
package cn.zuowenjun.boot.service;

import java.util.List;

import org.springframework.web.multipart.MultipartFile;

import cn.zuowenjun.boot.domain.*;


public interface GoodsService {
    
    List<Goods> getGoodsListByPage(int pageSize,int pageNo);
    
    List<Goods> getGoodsListByCategory(int categoryId);
    
    List<Goods> getGoodsListByMultIds(int...goodsIds);
    
    Goods getGoods(int id);
    
    void insertGoods(Goods goods,MultipartFile uploadGoodsPic);
    
    void updateGoods(Goods goods,MultipartFile uploadGoodsPic);
    
    void deleteGoods(int id);
    
    List<GoodsCategory> getAllGoodsCategoryList();
    
    void insertGoodsCategory(GoodsCategory goodsCategory);
    
    void updateGoodsCategory(GoodsCategory goodsCategory);
    
    void deleteGoodsCategory(int id);
    
}

//ShoppingOrderService.java
package cn.zuowenjun.boot.service;

import java.util.List;

import cn.zuowenjun.boot.domain.*;

public interface ShoppingOrderService {
    
    ShoppingOrder getShoppingOrder(int id);
    
    List<ShoppingOrder> getShoppingOrderList(String shopper);
    
    List<ShoppingOrderDetail> getShoppingOrderDetail(int orderId);
    
    boolean createShoppingOrderByShopper(String shopper);
    
    void insertShoppingOrderWithDetail(ShoppingOrder order,List<ShoppingOrderDetail> orderDetails);
    
    void deleteShoppingOrderDetail(int orderDetailId);
    
    void deleteShoppingOrderWithDetail(int orderId);
    
    void updateShoppingOrder(ShoppingOrder order);
    
    List<ShoppingCart> getShoppingCartList(String shopper);
    
    int getShoppingCartBuyCount(String shopper);
    
    void insertShoppingCart(ShoppingCart shoppingCart);
    
    void deleteShoppingCart(int shoppingCartId);
    
    void clearShoppingCart(String shopper);
    
    
}
View Code

  如上代碼示,我僅定義了三個service接口,分別是:ShopUserService(用戶服務)、GoodsService(商品服務【含:商品類別、商品信息】)、ShoppingOrderService(購物訂單服務【含:購物車、購物訂單、購物訂單明細】),我說過服務層不必定是與DB中的表一 一對應的,而是應該體現服務內聚(即:業務領域),若是單純的與DAO層同樣,一個service與一個dao對應,那就失去了分層的意義,並且還增長了複雜度。我的見解。

  4.2在cn.zuowenjun.boot.service.impl包中實現4.1中相關的業務領域服務接口(代碼很簡單,主要是實現接口的一些方法,惟一有點特別是文件上傳,事務,記錄日誌,這些經過代碼就能看明白就再也不詳情描述了)

//ShopUserServiceImpl.java
package cn.zuowenjun.boot.service.impl;

import java.util.List;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import cn.zuowenjun.boot.EShopProperties;
import cn.zuowenjun.boot.domain.ShopUser;
import cn.zuowenjun.boot.mapper.ShopUserMapper;
import cn.zuowenjun.boot.service.ShopUserService;

@Service
public class ShopUserServiceImpl implements ShopUserService {

    private ShopUserMapper shopUserMapper;
    
    private EShopProperties shopProperties;
    
    @Autowired
    public ShopUserServiceImpl(ShopUserMapper shopUserMapper,EShopProperties shopProperties) {
        this.shopUserMapper=shopUserMapper;
        this.shopProperties=shopProperties;
    }

    @Override
    public List<ShopUser> getAll() {
        return shopUserMapper.selectAll();
    }

    @Override
    public ShopUser get(String userId) {
        return shopUserMapper.selectByPrimaryKey(userId);
    }
    
    @Override
    public String getCurrentLoginUser() {
        if(getRequest().getSession().getAttribute("loginUser")==null) {
            return null;
        }
        
        return getRequest().getSession().getAttribute("loginUser").toString();
        
    }
    

    @Override
    public String login(String uid, String pwd) {
        if(shopProperties.getShopUserId().equalsIgnoreCase(uid) && 
                shopProperties.getShopUserPwd().equals(pwd)) {
            getRequest().getSession().setAttribute("loginUser", uid);
            return null;
        }else {
            return "用戶名或密碼不正確!";
        }
    }

    @Override
    public void logout() {
        getRequest().getSession().removeAttribute("loginUser");
    }
    
    
    private HttpServletRequest getRequest() {
        HttpServletRequest  request= ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
        return request;
    }


    
    
}



//GoodsServiceImpl.java
package cn.zuowenjun.boot.service.impl;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.UUID;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import cn.zuowenjun.boot.domain.*;
import cn.zuowenjun.boot.mapper.GoodsCategoryMapper;
import cn.zuowenjun.boot.mapper.GoodsMapper;
import cn.zuowenjun.boot.service.GoodsService;

@Service
public class GoodsServiceImpl implements GoodsService {
    
    private static Logger logger=LoggerFactory.getLogger(GoodsServiceImpl.class);
    
    @Autowired
    private GoodsMapper goodsMapper;
    
    @Autowired
    private GoodsCategoryMapper categoryMapper;
    
    @Override
    public List<Goods> getGoodsListByPage(int pageSize,int pageNo){
        return goodsMapper.getListByPage(pageSize, pageNo);
    }
    
    @Override
    public List<Goods> getGoodsListByCategory(int categoryId) {
        return goodsMapper.getList(categoryId);
    }
    
    @Override
    public List<Goods> getGoodsListByMultIds(int... goodsIds) {
        return goodsMapper.getListByMultIds(goodsIds);
    }
    
    @Override
    public Goods getGoods(int id) {
        return goodsMapper.get(id);
    }
    
    @Transactional
    @Override
    public void insertGoods(Goods goods, MultipartFile uploadGoodsPic) {
        String picPath= saveGoodsPic(uploadGoodsPic);
        if(picPath!=null && !picPath.isEmpty()) {
            
            goods.setPicture(picPath);
        }
        goodsMapper.insert(goods);
        
        GoodsCategory gcate= categoryMapper.get(goods.getCategoryId());
        gcate.setGoodsCount(gcate.getGoodsCount()+1);
        categoryMapper.update(gcate);
        
        logger.info("inserted new goods - id:" + goods.getId());
    }
    
    @Override
    public void updateGoods(Goods goods,MultipartFile uploadGoodsPic) {
        String picPath= saveGoodsPic(uploadGoodsPic);
        if(picPath!=null && !picPath.isEmpty()) {
            
            goods.setPicture(picPath);
        }
         goodsMapper.update(goods);
         
         logger.info("update goods - id:" + goods.getId());
    }
    
    @Transactional
    @Override
    public void deleteGoods(int id) {
        Goods g= goodsMapper.get(id);
        goodsMapper.delete(g.getId());
        
        GoodsCategory gcate= categoryMapper.get(g.getCategoryId());
        gcate.setGoodsCount(gcate.getGoodsCount()-1);
        categoryMapper.update(gcate);
        
        //若是有圖片,則同時刪除圖片
        if(g.getPicture()!=null && !g.getPicture().isEmpty()) {
            
            String picPath= getRequest().getServletContext().getRealPath("/") + g.getPicture();
            File file = new File(picPath);
            if(file.exists()) {
                file.delete();
            }
        }
        
        logger.info("deleted goods - id:" + g.getId());
    }
    
    @Override
    public List<GoodsCategory> getAllGoodsCategoryList(){
        return categoryMapper.getAll();
    }
    
    @Override
    public void insertGoodsCategory(GoodsCategory goodsCategory) {
        categoryMapper.insert(goodsCategory);
    }
    
    @Override
    public void updateGoodsCategory(GoodsCategory goodsCategory) {
        categoryMapper.update(goodsCategory);
    }
    
    @Override
    public void deleteGoodsCategory(int id) {
        categoryMapper.delete(id);
    }


    private String saveGoodsPic(MultipartFile uploadGoodsPic) {
        
        if(uploadGoodsPic==null || uploadGoodsPic.isEmpty()) {
            return null;
        }
        
        String fileName = uploadGoodsPic.getOriginalFilename();
        
        String extName = fileName.substring(fileName.lastIndexOf("."));
        
        String newFileName=UUID.randomUUID().toString()+extName;
        File file = new File(getFileSavePath(newFileName));
        if(!file.exists()) {
            file.getParentFile().mkdirs();
        }
        
        
        try {
            uploadGoodsPic.transferTo(file);
            //return file.toURI().toURL().toString();
            return getUrlPath(file.getAbsolutePath());
            
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        return null;
    }



    private String getFileSavePath(String fileName) {
        String realPath =getRequest().getServletContext().getRealPath("/uploadimgs/");
        return realPath + fileName;
    }

    private String getUrlPath(String filePath) {
        String rootPath= getRequest().getServletContext().getRealPath("/");
        return filePath.replace(rootPath, "").replaceAll("\\\\", "/");
    }

    private HttpServletRequest getRequest() {
        HttpServletRequest  request= ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
        return request;
    }
    
    
}

//ShoppingOrderServiceImpl.java
package cn.zuowenjun.boot.service.impl;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import cn.zuowenjun.boot.domain.*;
import cn.zuowenjun.boot.mapper.*;
import cn.zuowenjun.boot.service.ShoppingOrderService;

@Service
public class ShoppingOrderServiceImpl implements ShoppingOrderService  {

    @Autowired
    private ShoppingOrderMapper  orderMapper;
    
    @Autowired
    private ShoppingOrderDetailMapper orderDetailMapper;
    
    @Autowired
    private ShoppingCartMapper shoppingCartMapper;
    
    @Autowired
    private ShoppingOrderExtMapper shoppingOrderExtMapper;
    
    @Override
    public void insertShoppingCart(ShoppingCart shoppingCart) {
        ShoppingCart cart=shoppingCartMapper.get(shoppingCart.getShopper(), shoppingCart.getGoodsId());
        if(cart==null) {
            shoppingCartMapper.insert(shoppingCart);
        }else {
            cart.setQty(cart.getQty()+shoppingCart.getQty());
            shoppingCartMapper.update(cart);
        }
    }
    
    @Override
    public void deleteShoppingCart(int shoppingCartId) {
        shoppingCartMapper.deleteItem(shoppingCartId);
    }
    
    
    @Override
    public ShoppingOrder getShoppingOrder(int id) {
        return orderMapper.selectByPrimaryKey(id);
    }
    
    @Override
    public List<ShoppingOrder> getShoppingOrderList(String shopper) {
        return shoppingOrderExtMapper.selectAllByShopper(shopper);
    }

    @Override
    public List<ShoppingOrderDetail> getShoppingOrderDetail(int orderId) {
        return orderDetailMapper.selectByOrderId(orderId);
    }

    @Transactional
    @Override
    public boolean createShoppingOrderByShopper(String shopper) {
        
        List<ShoppingCart> carts= shoppingCartMapper.getList(shopper);
        if(carts==null || carts.size()<=0) {
            return false;
        }
        
        int totalQty=0;
        BigDecimal totalPrc=BigDecimal.valueOf(0);
        List<ShoppingOrderDetail> orderDetails=new ArrayList<>();
        
        for(ShoppingCart c:carts) {
            totalQty+=c.getQty();
            BigDecimal itemPrc=c.getInGoods().getPrice().multiply(BigDecimal.valueOf(c.getQty()));
            totalPrc=totalPrc.add(itemPrc);
            ShoppingOrderDetail od=new ShoppingOrderDetail();
            od.setGoodsid(c.getGoodsId());
            od.setQty(c.getQty());
            od.setTotalprice(itemPrc);
            od.setCreateby(shopper);
            od.setCreatetime(new Date());
            
            orderDetails.add(od);
        }
        
        ShoppingOrder order=new ShoppingOrder();
        order.setShopper(shopper);
        order.setTotalqty(totalQty);
        order.setTotalprice(totalPrc);
        order.setCreateby(shopper);
        order.setCreatetime(new Date());
        order.setIscompleted(false);
        
        insertShoppingOrderWithDetail(order,orderDetails);
        
        clearShoppingCart(shopper);
        
        return true;
    }
    
    @Transactional
    @Override
    public void insertShoppingOrderWithDetail(ShoppingOrder order, List<ShoppingOrderDetail> orderDetails) {
        
        orderMapper.insert(order);
        int orderId=order.getId();
        for(ShoppingOrderDetail od:orderDetails) {
            od.setShoppingorderid(orderId);
            orderDetailMapper.insert(od);
        }
    }
    

    @Override
    public void deleteShoppingOrderDetail(int orderDetailId) {
        
        orderDetailMapper.deleteByPrimaryKey(orderDetailId);
    }

    @Transactional
    @Override
    public void deleteShoppingOrderWithDetail(int orderId) {
        
        orderMapper.deleteByPrimaryKey(orderId);
        orderDetailMapper.deleteByOrderId(orderId);
    }

    @Override
    public void updateShoppingOrder(ShoppingOrder order) {
        orderMapper.updateByPrimaryKey(order);
    }

    @Override
    public List<ShoppingCart> getShoppingCartList(String shopper) {
        return shoppingCartMapper.getList(shopper);
    }

    @Override
    public int getShoppingCartBuyCount(String shopper) {
        return shoppingCartMapper.getBuyCount(shopper);
    }

    @Override
    public void clearShoppingCart(String shopper) {
        shoppingCartMapper.delete(shopper);
    }





}
View Code

  4.3編寫基於VUE前端框架實現的相關UI界面

  4.3.1.VUE是什麼?如何使用VUE前端框架設計頁面?認真閱讀官方中文教程就能夠了:https://cn.vuejs.org/v2/guide/index.html ,這裏只是着重說明一下,VUE是實現了MVVM框架,使用VUE的核心組件:模板、路由、數據雙向綁定等特性可以設計出很牛逼的SPA(單WEB頁面的多UI交互的應用),本人(夢在旅途)VUE只是初學者,故在本示例中我只是使用VUE的最基本的一些功能屬性(如:el:指定VUE的渲染範圍(綁定的做用域)、data(數據模型MODEL)、computed(動態計算屬性)、created(VUE初始化後觸發的事件)、methods(綁定自定義方法))

  4.3.2.因爲採用先後端分離,徹底能夠一個項目全是靜態的VUE HTML模板,另外一個項目是基於spring boot REST Api項目,但這裏是演示,故採起在同一個項目中,我這裏是在webapp目錄下建立相關的HTML視圖頁面,若是不在同一個項目中,注意基於spring boot REST Api項目中須要設置可以容許跨域訪問,全部HTML視圖代碼以下:

index.html(商品列表,主頁)

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>夢在旅途的電商小店Demo-Power by Spring Boot+MyBatis-Boot</title>
<meta name="author" content="www.zuowenjun.cn" >
<script src="https://cdn.jsdelivr.net/npm/vue" type="text/javascript"></script>
<script src="https://cdn.staticfile.org/vue-resource/1.5.1/vue-resource.min.js"></script>
<style type="text/css">
    #catesbox ul li{float:left;padding:5px;margin-right:20px;border:1px solid green;display: inline-block;cursor:pointer;}
    .clfx {clear:both;display:block;}
    .gpic{width:100px;height:100px;text-align:center;vertical-align:middle;}
    #goodsbox table {width:100%;border-collapse:collapse;}
    #goodsbox table tr >*{border:1px solid blue;padding:5px;}
    li.active{background-color:orange;font-weight:bold;}
    #copy{
        margin-top:20px;
        text-align: center;
    }
    body{padding-top:51px;}
    #topbar{height:50px;line-height:50px;margin:0;width:100%;background-color:WhiteSmoke;
        position: fixed;top:0;border-bottom:1px solid darkgray;text-align: right;}
</style>
</head>
<body>
<div id="app">
        <div id="topbar">
        <a href="/cartlist.html" target="_blank">購物車(已加入商品數量:{{cartCount}})</a>&nbsp;|&nbsp;
        <a href="/orderlist.html" target="_blank">訂單中心</a>&nbsp;|&nbsp;
        <a href="/admin.html" target="_blank">管理後臺</a>&nbsp;&nbsp;&nbsp;&nbsp;
        </div>
        <h2>商品類目:</h2>
        <div id="catesbox">
            <ul v-for="c in cates">
                <li v-on:click="getGoodsListByCategory(c)" v-bind:class="{active:c.categoryName==curcate}">{{c.categoryName}}({{c.goodsCount}})</li>
            </ul>
            <div class="clfx"></div>
        </div>
    <h2>當前瀏覽的商品分類:<span>{{curcate}}</span></h2>
        <div id="goodsbox">
            <table>
                <tr>
                    <th>商品圖片</th>
                    <th>商品標題</th>
                    <th>價格</th>
                    <th>操做</th>
                </tr>
                <tr v-for="g in goods">
                    <td><img v-bind:src="g.picture" class="gpic"></td>
                    <td><a v-bind:href="'/detail.html?gid=' + g.id" target="_blank">{{g.title}}</a></td>
                    <td>¥{{g.price}}</td>
                    <td><button v-on:click="addToShoppingCart(g)">加入購物車</button></td>
                </tr>
            </table>
        </div>
</div>
    <p id="copy">
    Copyright &copy;2019 &nbsp;
    www.zuowenjun.cn and zuowj.cnblogs.com demo all rights.
    </p>
    <script type="text/javascript">
        var vm = new Vue({
            el:"#app",
            data:{
                cartCount:0,
                cates:[],
                goods:[],
                curcate:"ALL"
            },
            created:function(){
                var self = this;
                 this.$http.get('/api/categorys').then(function(res){
                     self.cates=res.body;  
                     //alert(JSON.stringify(self.cates));
                    },function(){
                        alert("獲取categorys失敗!");
                    });
                 
                 this.$http.get('/api/cartlist').then(function(res){
                     self.cartCount=res.body.length;  
                     //alert(JSON.stringify(self.goods));
                    },function(){
                        alert("獲取購物車信息失敗!");
                    });
                 
                //按分頁檢索商品列表
                 this.getGoodsListByPage(10,1);
            },
            methods:{
                getGoodsListByCategory:function(cate){
                    var self = this;
                    //按類別檢索商品列表
                     this.$http.get('/api/goods/' + cate.id).then(function(res){
                         self.goods=res.body;   
                         self.curcate=cate.categoryName;
                         //alert(JSON.stringify(self.goods));
                        },function(){
                            alert("獲取goods失敗!");
                        });
                },
                getGoodsListByPage:function(ps,pno){
                    var self = this;
                    //按分頁檢索商品列表
                     this.$http.get('/api/goods' +'?pagesize='+ps +'&page=' + pno).then(function(res){
                         self.goods=res.body;  
                         self.curcate="ALL";
                         //alert(JSON.stringify(self.goods));
                        },function(){
                            alert("獲取goods失敗!");
                        });
                },
                addToShoppingCart:function(goods){
                    //加入購物車
                    var self = this;
                    var qty=prompt('請輸入購買數量',1);
                     this.$http.post('/api/addToShoppingCart',{goodsid:goods.id,goodsqty:qty}).then(function(res){
                         var rs=res.body;
                         alert(rs.msg);
                         self.cartCount=rs.data.cartCount;
                        },function(){
                            alert("加入購物車失敗");
                        });
                }
            }
        });

    </script>
</body>
</html>
View Code

detail.html(商品詳情)

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>商品詳情 -夢在旅途的電商小店</title>
<meta name="author" content="www.zuowenjun.cn" >
<script src="https://cdn.jsdelivr.net/npm/vue" type="text/javascript"></script>
<script src="https://cdn.staticfile.org/vue-resource/1.5.1/vue-resource.min.js"></script>
<style type="text/css">
        .clfx {clear:both;display:block;}
        .row{width:100%;margin:10px 0;}
        .lbox{float:left;width:40%;min-height: 100px;}
        .rbox{float:right;width:50%;}
        .rbox ul li{margin:50px auto;}
        body{padding-top:51px;}
        #topbar{height:50px;line-height:50px;margin:0;width:100%;background-color:WhiteSmoke;
        position: fixed;top:0;border-bottom:1px solid darkgray;text-align: right;}
</style>

</head>
<body>
    <div id="app">
        <div id="topbar">
        <a href="/cartlist.html" target="_blank">購物車(已加入商品數量:{{cartCount}})</a>&nbsp;|&nbsp;
        <a href="/admin.html" target="_blank">管理後臺</a>&nbsp;&nbsp;&nbsp;&nbsp;
        </div>
        <div class="row">
            <div class="lbox">
                <img :src="goods.picture" style="width:100%;height:100%;margin:0;padding:0;">
            </div>
            <div class="rbox">
                <ul>
                    <li><strong>{{goods.title}}</strong></li>
                    <li>價格:¥{{goods.price}}</li>
                    <li>購買數量:<input v-model="buyqty" value="1"></li>
                    <li>購買價格:<span>{{buyprice}}</span></li>
                    <li><button @click="addToShoppingCart">加入購物車</button></li>
                </ul>
            </div>
            <div class="clfx"></div>
        </div>
        <div class="row">
            <h2>商品詳細描述:</h2>
            <hr/>
            <p>{{goods.introduction}}</p>
        </div>
    </div>
    <script type="text/javascript">
        var vm=new Vue({
            el:"#app",
            data:{
                cartCount:0,
                buyqty:1,
                goods:{}
            },
            created:function(){
                var gid= getQueryString("gid");
                var self = this;

                 this.$http.get('/api/goods-' + gid).then(function(res){
                     self.goods=res.body;  
                     //alert(JSON.stringify(self.goods));
                    },function(){
                        alert("獲取goods失敗!");
                    });
                 
                 this.$http.get('/api/cartlist').then(function(res){
                     self.cartCount=res.body.length;  
                     //alert(JSON.stringify(self.goods));
                    },function(){
                        alert("獲取購物車信息失敗!");
                    });
                 
            },
            computed:{
                buyprice:function(){
                    return (this.buyqty * this.goods.price).toFixed(2);
                }
            },
            methods:{
                addToShoppingCart:function(){
                    //alert(this.buyqty);
                    //加入購物車
                    var self = this;
                     this.$http.post('/api/addToShoppingCart',{goodsid:this.goods.id,goodsqty:this.buyqty}).then(function(res){
                         var rs=res.body;
                         alert(rs.msg);
                         self.cartCount=rs.data.cartCount;
                        },function(){
                            alert("加入購物車失敗");
                        });
                }
            }
        });
        
        function getQueryString(name) { 
            var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i"); 
            var r = window.location.search.substr(1).match(reg); 
            if (r != null) return unescape(r[2]); 
            return null; 
        }
        
    </script>
</body>
</html>
View Code

cartlist.html(購物車)

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>購物車詳情 -夢在旅途的電商小店</title>
<meta name="author" content="www.zuowenjun.cn" >
<script src="https://cdn.jsdelivr.net/npm/vue" type="text/javascript"></script>
<script src="https://cdn.staticfile.org/vue-resource/1.5.1/vue-resource.min.js"></script>
<style type="text/css">
    .toolbar{margin:10px 5px;}
    .carttable{width:100%;margin:0px;padding:5px;border:1px solid gray;}
    .carttable tr >*{border-bottom:1px solid gray;padding:5px;text-align: center;}
    .buybtn{background-color:green;border:none;width:280px;padding:20px;color:white;font-size:20pt;}
    #copy{margin-top:20px;text-align: center;}
</style>
</head>
<body>
    <div id="app">
        <div class="toolbar">
            <button @click="deleteItem()" :disabled="carts.length==0">移出購物車</button>&nbsp;|&nbsp;
            <button @click="clearCart()" :disabled="carts.length==0">清空購物車</button>
        </div>
        <div>
            <table class="carttable">
                <tr>
                    <th>選擇</th>
                    <th>商品ID</th>
                    <th>商品名稱</th>
                    <th>預購買數量</th>
                    <th>價格</th>
                    <th>添加時間</th>
                </tr>
                <tr v-for="c in carts">
                    <td><input type="checkbox" class="chkitem" @click="checkitem(c,$event.target)" :checked="chkedItemIds.indexOf(c.id)>-1"></td>
                    <td>{{c.goodsId}}</td>
                    <td>{{c.inGoods.title}}</td>
                    <td>{{c.qty}}</td>
                    <td>¥{{(c.inGoods.price * c.qty).toFixed(2)}}</td>
                    <td>{{c.addedTime}}</td>
                </tr>
                <tr v-if="carts.length==0" style="text-align: center;">
                    <td colspan="6">空空如也,趕忙選購商品吧!~</td>
                </tr>
            </table>
        </div>
        <p style="text-align: center;">
            <button class="buybtn" @click="createOrder()" :disabled="carts.length==0">當即下單</button>
        </p>
    </div>
    <p id="copy">
    Copyright &copy;2019 &nbsp;
    www.zuowenjun.cn and zuowj.cnblogs.com demo all rights.
    </p>
    <script type="text/javascript">
        var vm=new Vue({
            el:"#app",
            data:{
                carts:[],
                chkedItemIds:[]
            },
            created:function(){
                var self = this;

                 this.$http.get('/api/cartlist').then(function(res){
                     self.carts=res.body;  
                     //alert(JSON.stringify(self.carts));
                    },function(){
                        alert("獲取購物車信息失敗!");
                    });
                 
            },
            methods:{
                checkitem:function(cart,chk){
                    //alert(chk.checked);
                    if(chk.checked){
                        this.chkedItemIds.push(cart.id);
                    }else{
                        this.chkedItemIds.remove(cart.id);
                    }
                },
                deleteItem:function(){
                    var self = this;
                    //alert(JSON.stringify(self.chkedItemIds));
                     this.$http.post('/api/deletecartitems-many',self.chkedItemIds).then(function(res){ 
                         self.carts= self.carts.filter(function(e){ return self.chkedItemIds.indexOf(e.id)<=-1;});
                         alert(res.body.msg);
                        },function(){
                            alert("刪除失敗!");
                        });
                },
                clearCart:function(){
                    var self = this;
                     this.$http.post('/api/deletecartitems-all').then(function(res){ 
                         self.carts=[];
                         alert(res.body.msg);
                        },function(){
                            alert("刪除失敗!");
                        });
                },
                createOrder:function(){
                    var self = this;
                     this.$http.post('/api/createorder').then(function(res){ 
                         alert(res.body.msg);
                         if(res.body.code==0){//如查下單成功,則清空購物車
                             self.carts=[];
                         }
                        },function(){
                            alert("下單失敗!");
                        });
                }
            }
        });
        
        Array.prototype.remove = function(val) { 
                var index = this.indexOf(val); 
                if (index > -1) { 
                this.splice(index, 1); 
                } 
            };
                
    </script>
</body>
</html>
View Code

orderlist.html(訂單中心)

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>訂單詳情 -夢在旅途的電商小店</title>
<script src="https://cdn.jsdelivr.net/npm/vue" type="text/javascript"></script>
<script src="https://cdn.staticfile.org/vue-resource/1.5.1/vue-resource.min.js"></script>
<style type="text/css">
    table{border:solid 1px blue;border-collapse: collapse;width:100%;margin:10px 1px;}
    table tr >*{border:solid 1px blue,padding:5px;border:dotted 1px gray;}
    .cfmbar{text-align: center;}
    .cfmbar button{border:none;background-color:blue;color:#ffffff;padding:10px 50px;}
    #copy{margin-top:20px;text-align: center;}
</style>
</head>
<body>
    <div id="app">
        <div>
            <h2>訂單列表:</h2>
            <table>
                <tr>
                    <th>訂單號</th>
                    <th>商品數量</th>
                    <th>訂單價格</th>
                    <th>完成否(收貨確認)</th>
                    <th>建立時間</th>
                    <th>查看訂單詳情</th>
                </tr>
                <tr v-for="o in shoppingOrders">
                    <td>{{o.id}}</td>
                    <td>{{o.totalqty}}</td>
                    <td>{{o.totalprice.toFixed(2)}}</td>
                    <td>{{o.iscompleted?"已收貨":"待收貨"}}</td>
                    <td>{{o.createtime}}</td>
                    <td><button @click="showOrderDetail(o)">查看</button></td>
                </tr>
                <tr v-if="shoppingOrders.length==0" style="text-align: center;">
                    <td colspan="6">沒有任何訂單信息!</td>
                </tr>
            </table>
        </div>
        <div v-if="viewOrder!=null">
            <h3>訂單號【{{viewOrder.id}}】詳情:</h3>
            <table>
                <tr>
                    <th>商品ID</th>
                    <th>商品名稱</th>
                    <th>購買數量</th>
                    <th>費用</th>
                </tr>
                <tr v-for="od in viewOrderDetails.details">
                    <td>{{od.goodsid}}</td>
                    <td>{{goodsName(od)}}</td>
                    <td>{{od.qty}}</td>
                    <td>¥{{od.totalprice.toFixed(2)}}</td>
                </tr>
            </table>
            <p class="cfmbar" v-if="!viewOrder.iscompleted">
                <button @click="confirmOrderCompleted(viewOrder)" >確認完成(已收貨)</button>
            </p>
        </div>
    </div>
    <p id="copy">
    Copyright &copy;2019 &nbsp;
    www.zuowenjun.cn and zuowj.cnblogs.com demo all rights.
    </p>
    <script type="text/javascript">
        var vm=new Vue({
            el:"#app",
            data:{
                shoppingOrders:[],
                viewOrder:null,
                viewOrderDetails:null
            },
            created:function(){
                var self = this;
                
                 this.$http.get('/api/orders').then(function(res){
                     self.shoppingOrders=res.body;  
                     //alert(JSON.stringify(self.shoppingOrders));
                    },function(){
                        alert("獲取orders失敗!");
                    });
            },
            computed:{
                goodsName(){//利用JS閉包實現傳參
                    return function(od){
                        var goods= this.viewOrderDetails.goodss.filter(function(g){return g.id==od.goodsid })[0];
                        //alert(od.goodsid);
                        return goods.title;
                    }
                }
            },
            methods:{
                showOrderDetail:function(o){
                     var self = this;
                     this.$http.post('/api/orderdetail',{orderId:o.id}).then(function(res){
                         if(res.body.code==0){
                             self.viewOrderDetails=res.body.data;  
                             //alert(JSON.stringify(self.viewOrderDetails));
                         }else{
                             alert(res.body.msg);
                             self.viewOrderDetails=null;
                             o=null;
                         }
                         self.viewOrder=o;
                        },function(){
                            alert("獲取orderdetail失敗!");
                        });
                },
                confirmOrderCompleted:function(o){
                    var self = this;
                    this.$http.post('/api/confirmOrderCompleted',{orderId:o.id}).then(function(res){
                        alert(res.body.msg);
                        if(res.body.code==0){
                            self.viewOrder.iscompleted=true;
                        }
                    }),function(){
                        alert("確認訂單完成失敗!");
                    };
                }
            }
        });
        
        function getQueryString(name) { 
            var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i"); 
            var r = window.location.search.substr(1).match(reg); 
            if (r != null) return unescape(r[2]); 
            return null; 
        }
        
        
    </script>
</body>
</html>
View Code

admin.html(管理後臺,因爲DEMO,故只實現商品的增、刪功能,其他管理功能未實現,僅做演示)

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>管理後臺 -夢在旅途的電商小店</title>
<meta name="author" content="www.zuowenjun.cn" >
<script src="https://cdn.jsdelivr.net/npm/vue" type="text/javascript"></script>
<script src="https://cdn.staticfile.org/vue-resource/1.5.1/vue-resource.min.js"></script>
<style type="text/css">
    table{border:solid 1px blue;border-collapse: collapse;width:100%;margin:10px 1px;}
    table tr >*{border:solid 1px blue,padding:5px;border:dotted 1px gray;}
    .gpic{width:100px;height:100px;text-align:center;vertical-align:middle;}
</style>
</head>
<body>
    <div id="app">
        <fieldset>
            <legend>管理商品:</legend>
            <table>
                <colgroup>
                    <col style="width:auto">
                    <col style="width:auto">
                    <col style="width:100px">
                    <col style="width:300px">
                    <col style="width:auto">
                    <col style="width:auto">
                    <col style="width:auto">
                    <col style="width:auto">
                    <col style="width:auto">
                </colgroup>
                <tr>
                    <th>商品ID</th>
                    <th>商品名稱</th>
                    <th>商品圖片</th>
                    <th>商品介紹</th>
                    <th>單價</th>
                    <th>類別ID</th>
                    <th>最後編輯者</th>
                    <th>最後編輯時間</th>
                    <th>操做</th>
                </tr>
                <tr style="background-color:orange;">
                    <td>{{editgoods.id}}</td>
                    <td><input type="text" v-model="editgoods.title"></td>
                    <td><img v-bind:src="editgoods.picture" class="gpic">
                    <input class="upload" type="file" id="gpicfile" @change="selectimg($event.target)" accept="image/png,image/gif,image/jpeg"></td>
                    <td><textarea v-model="editgoods.introduction"></textarea></td>
                    <td><input type="text" v-model="editgoods.price"></td>
                    <td>
                        <select v-model="editgoods.categoryId">
                            <option v-for="c in categorys" v-bind:value="c.id">{{c.categoryName}}</option>
                        </select>
                    </td>
                    <td>{{editgoods.lastEditBy}}</td>
                    <td>{{editgoods.lastEditTime}}</td>
                    <td><button @click="savegoods(editgoods)">保存</button></td>
                </tr>
                <tr v-for="g in goodss">
                    <td>{{g.id}}</td>
                    <td>{{g.title}}</td>
                    <td><img v-bind:src="g.picture" class="gpic"></td>
                    <td>{{g.introduction}}</td>
                    <td>{{g.price}}</td>
                    <td>{{g.categoryId}}</td>
                    <td>{{g.lastEditBy}}</td>
                    <td>{{g.lastEditTime}}</td>
                    <td><button @click="editgoods(g)" disabled="disabled">修改</button>&nbsp;|&nbsp;<!-- UI暫不實現修改,禁用 -->
                    <button @click="delgoods(g)">刪除</button></td>
                </tr>
            </table>
        </fieldset>
    </div>
    <script type="text/javascript">
        var vm=new Vue({
            el:"#app",
            data:{
                categorys:[],
                goodss:[],
                editgoods:{
                    id:null,
                    title:null,
                    picture:null,
                    price:0.00,
                    introduction:null,
                    categoryId:1,
                    lastEditBy:"zuowenjun",
                    lastEditTime:null
                }
            },
            created:function(){
                this.$http.get('/api/categorys').then(function(res){
                     this.categorys=res.body;  
                    },function(){
                        alert("獲取categorys失敗!");
                    });
                
                this.getGoodsListByPage(100,1);//DEMO,只加載第1頁
                 
            },
            methods:{
                getGoodsListByPage:function(ps,pno){
                    var self = this;
                    //按分頁檢索商品列表
                     this.$http.get('/api/goods' +'?pagesize='+ps +'&page=' + pno).then(function(res){
                         self.goodss=res.body;  
                         //alert(JSON.stringify(self.goods));
                        },function(){
                            alert("獲取goods失敗!");
                        });
                },
                selectimg:function(el){
                    
                    let gpic=el.files[0];
                    let type=gpic.type;//文件的類型,判斷是不是圖片
                    let size=gpic.size;//文件的大小,判斷圖片的大小
                    if('image/gif, image/jpeg, image/png, image/jpg'.indexOf(type) == -1){
                        alert('請選擇咱們支持的圖片格式!');
                        return false;
                    }
                    if(size>3145728){
                        alert('請選擇3M之內的圖片!');
                        return false;
                    }
                    var uri ='';
                    
                    this.editgoods.picture=URL.createObjectURL(gpic);
                },
                savegoods:function(g){
                    var fileDom=document.getElementById("gpicfile");
                    let formData = new FormData();
                    formData.append('id', this.editgoods.id);
                    formData.append('title', this.editgoods.title);
                    formData.append('picture', fileDom.files[0]);
                    formData.append('price', this.editgoods.price);
                    formData.append('introduction', this.editgoods.introduction);
                    formData.append('categoryId', this.editgoods.categoryId);
                    
                    let config = {
                              headers: {
                                'Content-Type': 'multipart/form-data'
                              }
                            }
                    
                      this.$http.post('/api/savegoods', formData, config).then(function (res) {
                          alert(res.body.msg);
                          if(res.body.code==0){
                              this.goodss.unshift(res.body.data);//插入到數組最新面
                              this.editgoods={//從新初始化,以便實現清空全部編輯框
                                      id:null,
                                    title:null,
                                    picture:null,
                                    price:0.00,
                                    introduction:null,
                                    categoryId:1,
                                    lastEditBy:"zuowenjun",
                                    lastEditTime:null
                                };
                          }
                        });
                },
                delgoods:function(g){
                     this.$http.get('/api/delgoods/' + g.id).then(function(res){
                         alert(res.body.msg); 
                         if(res.body.code==0){
                             this.goodss.remove(g);
                         }
                        },function(){
                            alert("刪除goods失敗!");
                        });
                }
            }
        });
    
        
        Array.prototype.remove = function(val) { 
            var index = this.indexOf(val); 
            if (index > -1) { 
            this.splice(index, 1); 
            } 
        };
    </script>
    
</body>
</html>
View Code

前端交互所須要API(由於是DEMO,故全部的API ACTION方法都在Apicontroller中),代碼以下:

package cn.zuowenjun.boot.controller;

import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import cn.zuowenjun.boot.domain.*;
import cn.zuowenjun.boot.service.*;

/*
 * ALL REST API 
 */
@RestController
@RequestMapping("/api")
public class ApiController {

    @Autowired
    private GoodsService goodsService;

    @Autowired
    private ShoppingOrderService shoppingOrderService;

    @Autowired
    private ShopUserService shopUserService;
    
    private String getCurrentShopper() {
        String shopper = shopUserService.getCurrentLoginUser();
        return shopper;
    }
    
    @PostMapping(value="/login",produces = "application/json;charset=utf-8")
    public ApiResultMsg login(@RequestBody Map<String,String> requestMap) {
        String userid=requestMap.get("userid");
        String upwd=requestMap.get("upwd");
        String loginResult= shopUserService.login(userid, upwd);
        if(loginResult==null) {
            return new ApiResultMsg(0,"OK",null);
        }else {
            return new ApiResultMsg(-1,"登陸失敗:" + loginResult,null);
        }
        
    }

    @GetMapping(value = "/categorys", produces = "application/json;charset=utf-8")
    public List<GoodsCategory> getAllGoodsCategorys() {
        return goodsService.getAllGoodsCategoryList();
    }

    @GetMapping(value = "/goods/{cateid}", produces = "application/json;charset=utf-8")
    public List<Goods> getGoodsList(@PathVariable(name = "cateid") int categoryid) {
        return goodsService.getGoodsListByCategory(categoryid);
    }

    @GetMapping(value = "/goods", produces = "application/json;charset=utf-8")
    public List<Goods> getGoodsList(@RequestParam(name = "pagesize", required = false) String spageSize,
            @RequestParam(name = "page", required = false) String spageNo) {

        int pageSize = tryparseToInt(spageSize);
        int pageNo = tryparseToInt(spageNo);

        pageSize = pageSize <= 0 ? 10 : pageSize;
        pageNo = pageNo <= 1 ? 1 : pageNo;
        return goodsService.getGoodsListByPage(pageSize, pageNo);
    }

    @GetMapping(value = "/goodsmany", produces = "application/json;charset=utf-8")
    public List<Goods> getGoodsListByMultIds(@RequestBody int[] ids) {
        return goodsService.getGoodsListByMultIds(ids);
    }

    @PostMapping(value = "/addToShoppingCart", produces = "application/json;charset=utf-8")
    public ApiResultMsg addToShoppingCart(@RequestBody Map<String, Integer> json) {
        int goodsId = json.get("goodsid");
        int qty = json.get("goodsqty");
        ApiResultMsg msg = new ApiResultMsg();
        if (goodsId <= 0) {
            msg.setCode(101);
            msg.setMsg("該商品ID無效");
            return msg;
        }

        String shopper = getCurrentShopper();
        ShoppingCart shoppingCart = new ShoppingCart(0, shopper, goodsId, qty, new Date());

        shoppingOrderService.insertShoppingCart(shoppingCart);

        msg.setCode(0);
        msg.setMsg("添加購物車成功!");

        int cartCount = shoppingOrderService.getShoppingCartBuyCount(shopper);
        HashMap<String, Object> data = new HashMap<>();
        data.put("cartCount", cartCount);

        msg.setData(data);

        return msg;
    }

    @GetMapping(value = "/goods-{gid}", produces = "application/json;charset=utf-8")
    public Goods getGoods(@PathVariable("gid") int goodsId) {
        return goodsService.getGoods(goodsId);
    }

    @GetMapping(value = "/cartlist", produces = "application/json;charset=utf-8")
    public List<ShoppingCart> getShoppingCartList() {
        String shopper = getCurrentShopper();
        return shoppingOrderService.getShoppingCartList(shopper);
    }

    @PostMapping(value = "/deletecartitems-{mode}", produces = "application/json;charset=utf-8")
    public ApiResultMsg deleteShoppingCartItems(@PathVariable("mode") String mode,
            @RequestBody(required = false) int[] cartIds) {
        if (mode.equalsIgnoreCase("all")) {
            String shopper = getCurrentShopper();
            shoppingOrderService.clearShoppingCart(shopper);
        } else {
            for (int id : cartIds) {
                shoppingOrderService.deleteShoppingCart(id);
            }
        }

        return new ApiResultMsg(0, "刪除成功!", null);
    }

    @PostMapping(value = "/createorder", produces = "application/json;charset=utf-8")
    public ApiResultMsg createShoppingOrder() {

        String shopper = getCurrentShopper();
        ApiResultMsg msg = new ApiResultMsg();
        if (shoppingOrderService.createShoppingOrderByShopper(shopper)) {
            msg.setCode(0);
            msg.setMsg("恭喜你,下單成功!");
        } else {
            msg.setCode(101);
            msg.setMsg("對不起,下單失敗,請重試!");
        }

        return msg;

    }

    @RequestMapping(path = "/orders", produces = "application/json;charset=utf-8", method = RequestMethod.GET) // 等同於@GetMapping
    public List<ShoppingOrder> getShoppingOrderList() {
        String shopper = getCurrentShopper();
        return shoppingOrderService.getShoppingOrderList(shopper);
    }

    @RequestMapping(path = "/orderdetail", produces = "application/json;charset=utf-8", method = RequestMethod.POST) // 等同於@PostMapping
    public ApiResultMsg getShoppingOrderDetail(@RequestBody Map<String, String> requestJosn) {
        String orderId = requestJosn.get("orderId");
        List<ShoppingOrderDetail> orderDetails = shoppingOrderService.getShoppingOrderDetail(tryparseToInt(orderId));
        ApiResultMsg msg = new ApiResultMsg();
        if (orderDetails.size() > 0) {

            int[] goodsIds = new int[orderDetails.size()];
            for (int i = 0; i < orderDetails.size(); i++) {
                goodsIds[i] = orderDetails.get(i).getGoodsid();
            }

            List<Goods> goodsList = goodsService.getGoodsListByMultIds(goodsIds);
            HashMap<String, Object> data = new HashMap<>();
            data.put("details", orderDetails);
            data.put("goodss", goodsList);
            
            msg.setCode(0);
            msg.setData(data);

        } else {
            msg.setCode(101);
            msg.setMsg("獲取訂單詳情信息失敗!");
        }
        
        return msg;

    }
    
    //這裏示例配置多個URL請求路徑
    @PostMapping(path= {"/confirmOrderCompleted","/cfmordercompl"},produces="application/json;charset=utf-8")
    public ApiResultMsg confirmOrderCompleted(@RequestBody Map<String, String> requestJosn) {
        String reqOrderId = requestJosn.get("orderId");
        ApiResultMsg msg=new ApiResultMsg();
        try {
            
            int orderId=tryparseToInt(reqOrderId);
            ShoppingOrder  order= shoppingOrderService.getShoppingOrder(orderId);
            order.setIscompleted(true);
            shoppingOrderService.updateShoppingOrder(order);
            msg.setCode(0);
            msg.setMsg("確認訂單完成成功(已收貨)");
        }catch (Exception e) {
            msg.setCode(101);
            msg.setMsg("確認訂單完成失敗:" + e.getMessage());
        }
        
        return msg;
    }
    
    
    @PostMapping(path="/savegoods",produces="application/json;charset=utf-8",consumes="multipart/form-data")
    public ApiResultMsg saveGoods(@RequestParam("picture") MultipartFile gpic,HttpServletRequest request) {
        ApiResultMsg msg=new ApiResultMsg();
        try
        {
            Goods goods=new Goods();
            goods.setId(tryparseToInt(request.getParameter("id")));
            goods.setTitle(request.getParameter("title"));
            goods.setPrice(new BigDecimal(request.getParameter("price")));
            goods.setIntroduction(request.getParameter("introduction"));
            goods.setCategoryId(tryparseToInt(request.getParameter("categoryId")));
            goods.setLastEditBy(getCurrentShopper());
            goods.setLastEditTime(new Date());
            
            if(goods.getId()<=0) {
                goodsService.insertGoods(goods, gpic);
            } else {
                goodsService.updateGoods(goods, gpic);
            }
            
            msg.setCode(0);
            msg.setMsg("保存成功!");
            msg.setData(goods);
            
        }catch (Exception e) {
            msg.setCode(101);
            msg.setMsg("保存失敗:" + e.getMessage());
        }
        
        return msg;
        
    }
    
    @GetMapping(path="/delgoods/{gid}",produces="application/json;charset=utf-8")
    public ApiResultMsg deleteGoods(@PathVariable("gid") int goodsId) {
        goodsService.deleteGoods(goodsId);
        ApiResultMsg msg=new ApiResultMsg();
        msg.setCode(0);
        msg.setMsg("刪除商品成功!");
        
        return msg;
    }
    

    private int tryparseToInt(String str) {
        try {
            return Integer.parseInt(str);
        } catch (Exception e) {
            return -1;
        }
    }

}
View Code

REST API controller與普通的MVC controller用法上基本相同,只是REST API ACTION返回的是數據內容自己(@RestController或@Controller+@ResponseBody),而MVC ACTION通常返回view

 4.4添加身份認證攔截器、日誌記錄等

  由於演示的是電商購物場景,既有下單又有後臺管理,故這裏我增長了登陸視圖及登陸攔截器,以完成對部份頁面及API的權限控制,實現代碼以下:

  4.4.1.設計login.html(登陸)

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登陸入口  -夢在旅途的電商小店</title>
<meta name="author" content="www.zuowenjun.cn" >
<script src="https://cdn.jsdelivr.net/npm/vue" type="text/javascript"></script>
<script src="https://cdn.staticfile.org/vue-resource/1.5.1/vue-resource.min.js"></script>
</head>
<body>
    <div id="app">
        <form method="post" @submit.prevent="loginsubmit">
        <p>用戶ID:</p>
        <p><input type="text" v-model="uid"></p>
                <p>密碼:</p>
        <p><input type="password" v-model="pwd"></p>
        <p>
            <button type="submit">登陸</button>
        </p>
        </form>
    </div>
    <script type="text/javascript">
        var vm=new Vue({
            el:"#app",
            data:{
                uid:null,
                pwd:null
            },
            methods:{
                loginsubmit:function(){
                    
                     this.$http.post('/api/login',{userid:this.uid,upwd:this.pwd}).then(function(res){
                         var rs=res.body;
                         if(rs.code==0){
                             window.location.href="index.html";
                         }else{
                             alert(rs.msg);
                         }
                        },function(){
                            alert("登陸請求失敗!");
                        });
                }
            }
        });
    </script>
</body>
</html>
View Code

  4.4.2.自定義實現HandlerInterceptor的登陸驗證攔截器:LoginInterceptor,代碼以下:(注意我是將該攔截器放在根包中cn.zuowenjun.boot)

package cn.zuowenjun.boot;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import com.fasterxml.jackson.databind.ObjectMapper;

import cn.zuowenjun.boot.domain.ApiResultMsg;

@Component
public class LoginInterceptor implements HandlerInterceptor {

    private static Logger logger = LoggerFactory.getLogger(LoginInterceptor.class);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {

        HttpSession session = request.getSession();
        if (session.getAttribute("loginUser") == null) {// 未登陸則轉到登陸頁面
            boolean isAjaxRequest = false;
            boolean isAcceptJSON = false;
            if (request.getHeader("x-requested-with") != null
                    && request.getHeader("x-requested-with").equalsIgnoreCase("XMLHttpRequest")) {
                isAjaxRequest = true;
            }

            if (request.getHeader("Accept") != null && request.getHeader("Accept").contains("application/json")) {
                isAcceptJSON = true;
            }

            if(isAjaxRequest || isAcceptJSON) {
                
                //使用jackson序列化JSON
                ApiResultMsg msg=new ApiResultMsg(-1,"未登陸,禁止訪問",null);
                ObjectMapper mapper = new ObjectMapper();
                String msgJson= mapper.writeValueAsString(msg);
                
                responseJson(response,msgJson);
                
            }else {
                
                response.sendRedirect("/login.html");
            }
            return false;
        }

        return true;
    }

    private void responseJson(HttpServletResponse response, String json) throws Exception {
        PrintWriter writer = null;
        response.setCharacterEncoding("UTF-8");
        response.setContentType("applcation/json; charset=utf-8");
        try {
            writer = response.getWriter();
            writer.print(json);

        } catch (IOException e) {
            logger.error("response error", e);
        } finally {
            if (writer != null)
                writer.close();
        }
    }
}
View Code

  代碼比較簡單,主要是判斷session中是否有記錄登陸的用戶名,若是沒有則表示未登陸,而後根據是AJAX請求或須要返回JSON的狀況則返回JSON,不然直接跳轉到login.html頁面。

  4.4.3.自定義實現WebMvcConfigurer的配置類:SpringbootdemoAppConfigurer,重寫addInterceptors方法,在該方法中把LoginInterceptor實例加入到攔截器管道中,以便交由spring MVC進行管理,代碼以下:(一樣放在根包中)

package cn.zuowenjun.boot;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.*;

@Configuration
public class SpringbootdemoAppConfigurer implements WebMvcConfigurer {
    
    @Autowired
    private LoginInterceptor loginInterceptor;
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        InterceptorRegistration registration = registry.addInterceptor(loginInterceptor);
        registration.addPathPatterns("/**");
        registration.excludePathPatterns("/*.html","/uploadimgs/*","/error","/api/login","/api/categorys","/api/goods*","/api/goods/*",
                "/hello/*","/test/*");
    }

}
View Code

注意我這裏是攔截全部路徑,而後使用excludePathPatterns來排除不須要攔截的路徑,若是須要攔截的路徑比較少,能夠直接指定攔截的具體路徑,這樣就不用排除了。

  4.4.4.另外補充一個功能點說明:通常一個應用程度都會有日誌記錄,這裏也不能少,spring boot中默認實現了:slf4j+logback(slf4j是一個日誌門面接口,logback是slf4j接口的實現,這樣搭配比較好,能夠隨時更換日誌實現框架),先在application.properties配置日誌的基本參數,以下所示:(詳細集成配置,可參見:https://www.jianshu.com/p/88b03b03570c)

#logging.config=xxxx  #能夠指定單獨的日誌配置XML文件,進行更豐富的設置,這裏未採用
logging.level.root=info
logging.level.cn.zuowenjun.boot.mapper=debug
logging.file=springbootdemo.log

配置好後,而後在相應的類中直接使用便可,用法以下:(具體可見上面的GoodsServiceImpl代碼)

private static Logger logger=LoggerFactory.getLogger(GoodsServiceImpl.class);//經過日誌工廠得到一個日誌記錄實例

logger.info("日誌信息");//有多個日誌級別可用,但注意須要配置中的root級別相對應,好比目前是配置了info,若是使用debug,可能就不會輸出日誌到文件

  4.5效果展現:(所有采用HTML+AJAX靜態交互)

   經過以上的後端API代碼+基於VUE的前端HTML,就完成了一個簡單的電商物購DEMO,效果截圖以下:

  主頁:(加入購物車後,右上角的」購物車(已加入商品數量:0)「 數字變自動同步更新)

商品詳情:

購物車:

訂單管理:

後臺管理(商品管理):

添加商品後,自動加入到列表第一行,若是刪除則移除刪除的商品所在行

 

本文示例項目源碼,請參見GIT:https://github.com/zuowj/springbootdemo

相關文章
相關標籤/搜索