Java Web商城開發--實現店鋪註冊

DAO層

首先實現商鋪的DAO層,DAO(Data Access Object)主要用來封裝對數據庫的訪問操作。由於使用Mybatis,所以只需要定義DAO接口類,具體的數據庫操作在mapper文件ShopCategory.xml中實現

public interface ShopDao {
    //新增店鋪
    int insertShop(Shop shop);
}
<mapper namespace="com.tory.shop.dao.ShopDao">
    <insert id="insertShop" useGeneratedKeys="true" keyColumn="shop_id" keyProperty="shopId">
        INSERT INTO tb_shop
        (owner_id, area_id, shop_category,
         shop_name, shop_describe, shop_addr, shop_phone, shop_img,
         create_time, last_edit_time,
         enable_status, advice_message)
        VALUES (#{owner.userId}, #{area.areaId}, #{shopCategory.categoryId},
                #{shopName}, #{shopDescribe}, #{shopAddr}, #{shopPhone}, #{shopImg},
                #{createTime}, #{lastEditTime}, #{enableStatus}, #{adviceMessage})
    </insert>
</mapper>

DTO層

Data Transfer Object數據傳輸對象,該層負責屏蔽後端的實體層,用於將DAO取到的數據進行再次處理加工後返回給Service層。在實際的業務場景下,後端存儲的數據遠比用戶需要的數據要龐大和複雜,因此需要進行處理、組合之後再返回。

如下所示爲Shop的DTO類,在其中保存商鋪操作結果狀態及信息,以及返回的Shop對象。

public class ShopExecution {
    private int state;              //結果狀態
    private String stateInfo;       //結果信息
    private Shop shop;              //操作的商鋪
    private int shopCount;          //返回商鋪的數量
    private List<Shop> shopList;    //返回的商鋪列表

    //執行失敗時的構造器,只有狀態枚舉作爲參數
    public ShopExecution(ShopStateEnum stateEnum){
        this.state=stateEnum.getState();
        this.stateInfo=stateEnum.getStateInfo();
    }
    //執行增刪改成功時的構造器,傳入狀態枚舉和Shop對象
    public ShopExecution(ShopStateEnum stateEnum, Shop shop) {
        this.state = stateEnum.getState();
        this.stateInfo = stateEnum.getStateInfo();
        this.shop = shop;
    }
    //執行查詢成功時的構造器,返回了
    public ShopExecution(ShopStateEnum stateEnum, List<Shop> shopList) {
        this.state = stateEnum.getState();
        this.stateInfo = stateEnum.getStateInfo();
        this.shopList = shopList;
    }
    ......

其中用枚舉類型來儲存結果狀態,如下所示爲枚舉類ShopStateEnum的定義

public enum ShopStateEnum {
    CHECK(0, "審覈中"), OFFLINE(-1, "非法商鋪"), SUCCESS(1, "操作成功"), PASS(2, "通過認證"), INNER_ERROR(-1001, "操作失敗"), NULL_SHOPID(-1002, "ShopId爲空"), NULL_SHOP_INFO(-1003, "傳入了空的信息");

    private int state;
    private String stateInfo;

    //枚舉類的構造函數
    ShopStateEnum(int state, String stateInfo) {
        this.state=state;
        this.stateInfo=stateInfo;
    }

    //根據狀態值返回狀態枚舉對象
    public static ShopStateEnum stateOf(int index){
        for (ShopStateEnum state : values()){
            if (state.getState()==index)
                return state;
        }
        return null;
    }
    ......

Service層

在service層實現一個店鋪添加的操作。該方法傳入shop對象和shopImg圖片,首先進行判空操作,若shop對象爲空,則返回插入失敗的ShopExecution對象。否則爲shop增加一些初始屬性後將店鋪信息存入數據庫。然後調用saveImg()方法將圖片保存到服務器並返回圖片地址,最後將圖片地址信息更新到shop對象的數據庫中。在ImageUtil類中使用thumbnailator進行的圖片操作,實現記錄在:https://blog.csdn.net/theVicTory/article/details/106007111

@Service
public class ShopServiceImpl implements ShopService {
    @Autowired
    private ShopDao shopDao;

    @Override
    public ShopExecution addShop(Shop shop, CommonsMultipartFile shopImg) {
        //如果傳入shop對象爲空,則返回失敗的ShopExecution
        if (shop==null)
            return new  ShopExecution(ShopStateEnum.NULL_SHOP_INFO);
        //爲shop設置一些初始值屬性
        shop.setEnableStatus(0);
        shop.setCreateTime(new Date());
        shop.setLastEditTime(new Date());
        //存入店鋪信息
        int affectedRows=shopDao.insertShop(shop);
        if (affectedRows<=0)
            throw new RuntimeException("插入數據庫失敗!");
        else {
            if (shopImg!=null){
                saveShopImg(shop,shopImg);
                //更新店鋪的圖片信息
                affectedRows= shopDao.updateShop(shop);
                if (affectedRows<=0)
                    throw new RuntimeException("更新數據庫失敗!");
            }
        }
        //shop對象成功插入數據庫,返回ShopExecution
        return new ShopExecution(ShopStateEnum.CHECK,shop);
    }

    private void saveShopImg(Shop shop, CommonsMultipartFile shopImg) {
        String imgPath= FileUtil.getShopImagePath(shop.getShopId());
        String shopImgAddr= ImageUtil.generateThumbnail(shopImg,imgPath);
        shop.setShopAddr(shopImgAddr);
    }
}

Controller層

定義ShopManagementController類映射路徑爲/shop,用於處理店鋪註冊相關請求。

首先getRegisterView()方法映射爲/register的GET請求,返回店鋪的註冊頁面。

getInitInfo()用於返回註冊頁面所需要的初始化信息–店鋪類別列表categoryList和區域列表areaList。這裏使用Map<String, Object>來儲存返回的數據,之後由於添加了@ResponseBody註解,返回的內容會被轉換爲JSON格式。

registerShop()方法映射爲/register的POST請求,用於接收店鋪的信息並完成註冊。首先調用checkCode()方法比對驗證碼是否輸入正確。然後通過request.getParameter()取出shopStr,前端以JSON字符串的形式將Shop對象放在了字符串shopStr中,通過JSON的ObjectMapper的readValue()方法將其轉換爲Shop對象。接着提取POST請求中的圖片文件,首先將request轉換爲MultipartHttpServletRequest,然後調用getFile()獲取圖片文件。最後調用shopService儲存shop、shopImg對象,根據返回的ShopExecution判斷儲存操作是否成功,若成功則設置modelMap中的success字段爲true,否則設爲false並將錯誤信息保存在errMsg字段中。最後將儲存數據的modelMap返回給客戶端。

package com.tory.shop.controller.shop;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.tory.shop.dto.ShopExecution;
import com.tory.shop.entity.Area;
import com.tory.shop.entity.PersonInfo;
import com.tory.shop.entity.Shop;
import com.tory.shop.entity.ShopCategory;
import com.tory.shop.enums.ShopStateEnum;
import com.tory.shop.service.AreaService;
import com.tory.shop.service.ShopCategoryService;
import com.tory.shop.service.ShopService;
import com.tory.shop.util.CodeUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Controller
@RequestMapping("/shop")
public class ShopManagementController {
    @Autowired
    ShopService shopService;
    @Autowired
    ShopCategoryService shopCategoryService;
    @Autowired
    AreaService areaService;

    //返回店鋪註冊頁面
    @RequestMapping(value = "/register", method = RequestMethod.GET)
    public String getRegisterView() {
        return "shop/register";
    }

    //返回註冊頁面的初始化信息
    @RequestMapping("/init")
    @ResponseBody
    public Map<String, Object> getInitInfo() {
        Map<String, Object> modelMap = new HashMap<>();
        try {
            List<Area> areaList = areaService.getAreaList();
            List<ShopCategory> categoryList = shopCategoryService.getCategoryList(new ShopCategory());
            modelMap.put("categoryList", categoryList);
            modelMap.put("areaList", areaList);
            modelMap.put("success", true);
        } catch (Exception e) {
            modelMap.put("success", false);
            modelMap.put("errMsg", e.getMessage());
        }
        return modelMap;
    }

    //接收註冊店鋪的POST請求
    @RequestMapping(value = "/register", method = RequestMethod.POST)
    @ResponseBody
    public Map<String, Object> registerShop(HttpServletRequest request) {
        Map<String, Object> modelMap = new HashMap<>();
        //校驗驗證碼是否正確
        if (!CodeUtil.checkCode(request)){
            modelMap.put("success",false);
            modelMap.put("errMsg","驗證碼錯誤");
            return modelMap;
        }
        //將前端傳回的json字符串的shop數據轉爲shop對象
        String shopStr = request.getParameter("shopStr");
        ObjectMapper jsonMapper = new ObjectMapper();
        Shop shop;
        try {
            shop = jsonMapper.readValue(shopStr, Shop.class);
        } catch (JsonProcessingException e) {
            modelMap.put("success", false);
            modelMap.put("errMsg", e.getMessage());
            return modelMap;
        }
        //接收前端傳回的圖片文件
        MultipartHttpServletRequest mRequest = (MultipartHttpServletRequest) request;
        CommonsMultipartFile shopImg = (CommonsMultipartFile) mRequest.getFile("shopImg");
        //存儲shop和shopImg對象
        if (shop != null && shopImg != null) {
            PersonInfo owner = new PersonInfo();
            owner.setUserId(1);
            shop.setOwner(owner);
            ShopExecution shopExecution = shopService.addShop(shop, shopImg);
            //如果存儲成功將"success"設爲true,否則設爲false並返回錯誤信息
            if (shopExecution.getState() == ShopStateEnum.CHECK.getState())
                modelMap.put("success", true);
            else {
                modelMap.put("success", false);
                modelMap.put("errMsg", shopExecution.getStateInfo());
            }
        } else {
            modelMap.put("success", false);
            modelMap.put("errMsg", "店鋪信息不能爲空");
        }
        return modelMap;
    }
}

值得注意的是這裏使用multipartResolver來接收混合字符串和圖片文件的FormData類型POST請求,因此需要在spring-mvc.xml配置文件中註冊該bean,否則使用request.getParameter()接收FormData會爲空值null。而且bean的id必須爲multipartResolver。記得在POM中引入該類的依賴庫commons-fileupload:commons-fileupload

    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="maxUploadSize" value="10485760"/>   <!--文件最大10M=10485760字節-->
        <property name="defaultEncoding" value="UTF-8"/>
        <property name="resolveLazily" value="true"/>       <!--開啓文件延遲解析-->
    </bean>

CodeUtil.checkCode()實現如下,就是分別獲取request中傳來的用戶輸入的內容和session中儲存的內容進行比較,如果相同返回true

package com.tory.shop.util;

import com.google.code.kaptcha.Constants;

import javax.servlet.http.HttpServletRequest;

public class CodeUtil {
    public static boolean checkCode(HttpServletRequest request) {
        String expectedCode = (String) request.getSession().getAttribute(Constants.KAPTCHA_SESSION_KEY);
        String inputCode = (String) request.getParameter("inputCode");
        if (inputCode == null || !expectedCode.equals(inputCode))
            return false;
        else return true;
    }
}

前端頁面

在前端頁面實現簡單的店鋪信息註冊如下所示,這裏使用的是一個輕量級的UI庫:SUI
在這裏插入圖片描述使用CDN的方式引入SUI的相關文件,css文件在頁面加載之前,js文件在頁面加載之後。最後引入用於加載信息和提交Ajax請求的shopedit.js文件

<link rel="stylesheet"
	href="//g.alicdn.com/msui/sm/0.6.2/css/sm.min.css">
<link rel="stylesheet"
	href="//g.alicdn.com/msui/sm/0.6.2/css/sm-extend.min.css">
</head>
<body>
<div>
.......
</div>
<script type='text/javascript' src='//g.alicdn.com/sj/lib/zepto/zepto.js' charset='utf-8'></script>
<script type='text/javascript' src='//g.alicdn.com/msui/sm/0.6.2/js/sm.min.js' charset='utf-8'></script>
<script type='text/javascript' src='//g.alicdn.com/msui/sm/0.6.2/js/sm-extend.min.js' charset='utf-8'></script>
<script type='text/javascript' src='../resources/js/shop/shopedit.js' charset='utf-8'></script>
</body>

如下所示爲頁面的Javascript代碼,這裏使用的是zepto.js,它是一個和jQuery語法類似但更爲輕量的js庫。首先通過KaTeX parse error: Expected 'EOF', got '#' at position 51: …st信息並填充到頁面中。之後爲#̲submit添加點擊事件,提交….ajax()將formData發送給服務器。值得注意的是contentType指定發送數據的形式,默認爲"application/x-www-form-urlencoded",即一般的表格將數據編碼爲urlenconded的方式,但是如果需要傳輸文件則需要FormData而不是默認。這裏可以指定contentType爲false即不採用默認值,而是根據實際內容爲FormData。

$(function () {
    registerShop();

    function registerShop() {
        //獲取初始化信息並填充到頁面
        $.getJSON("/ShopDemo/shop/init", function (data) {
            if (data.success) {          //獲取店鋪分類和區域信息填充到頁面
                var categoryHtml = '';
                var areaHtml = '';
                data.categoryList.map(function (item, index) {
                    categoryHtml += '<option data-id="' + item.categoryId + '">' + item.categoryName + '</option>';
                });
                data.areaList.map(function (item, index) {
                    areaHtml += '<option data-id="' + item.areaId + '">' + item.areaName + '</option>';
                });
                $('#shop-category').html(categoryHtml);
                $('#area').html(areaHtml);
            }
        });

        //提交註冊信息
        $('#submit').click(function () {
            //獲取頁面shop的信息與圖片
            var shop = {};
            shop.shopName = $('#shop-name').val();
            shop.shopAddr = $('#shop-addr').val();
            shop.shopPhone = $('#shop-phone').val();
            shop.shopDescribe = $('#shop-desc').val();
            shop.shopCategory = {
                categoryId: $('#shop-category').find('option').not(function () {
                    return !this.selected;
                }).data('id')
            };
            shop.area = {
                areaId: $('#area').find('option').not(function () {
                    return !this.selected;
                }).data('id')
            };
            var shopImg = $('#shop-img')[0].files[0];

            //將shop信息和圖片封裝爲FormData並上傳給服務器
            var formData = new FormData();
            formData.append('shopStr', JSON.stringify(shop));
            formData.append('shopImg', shopImg);
            $.ajax({
                url: '/ShopDemo/shop/register',
                type: 'POST',
                data: formData,
                contentType: false,
                processData: false,
                cache: false,
                success: function (data) {
                    if (data.success) {
                        $.toast('註冊成功!');
                    } else {
                        $.toast('註冊失敗' + data.errMsg);
                    }
                }
            })
        })
    }
});

頁面中驗證碼的實現使用的是com.github.penggle:kaptcha庫,在pom.xml文件中引入該依賴後需要在web.xml文件中配置servlet如下,

<!--配置驗證碼生成工具kaptcha  -->
  <servlet>
    <!-- 生成Servlet -->
    <servlet-name>Kaptcha</servlet-name>
    <servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class>

    <!-- 是否有邊框 -->
    <init-param>
      <param-name>kaptcha.border</param-name>
      <param-value>no</param-value>
    </init-param>
    <!-- 字體顏色 -->
    <init-param>
      <param-name>kaptcha.textproducer.font.color</param-name>
      <param-value>red</param-value>
    </init-param>
    <!-- 圖片寬度 -->
    <init-param>
      <param-name>kaptcha.image.width</param-name>
      <param-value>135</param-value>
    </init-param>
    <!-- 使用哪些字符生成驗證碼 -->
    <init-param>
      <param-name>kaptcha.textproducer.char.string</param-name>
      <param-value>ACDEFHKPRSTWX345679</param-value>
    </init-param>
    <!-- 圖片高度 -->
    <init-param>
      <param-name>kaptcha.image.height</param-name>
      <param-value>50</param-value>
    </init-param>
    <!-- 字體大小 -->
    <init-param>
      <param-name>kaptcha.textproducer.font.size</param-name>
      <param-value>43</param-value>
    </init-param>
    <!-- 干擾線的顏色 -->
    <init-param>
      <param-name>kaptcha.noise.color</param-name>
      <param-value>black</param-value>
    </init-param>
    <!-- 字符個數 -->
    <init-param>
      <param-name>kaptcha.textproducer.char.length</param-name>
      <param-value>4</param-value>
    </init-param>
    <!-- 使用哪些字體 -->
    <init-param>
      <param-name>kaptcha.textproducer.font.names</param-name>
      <param-value>Arial</param-value>
    </init-param>
  </servlet>
  <!-- 映射的url -->
  <servlet-mapping>
    <servlet-name>Kaptcha</servlet-name>
    <url-pattern>/Kaptcha</url-pattern>
  </servlet-mapping>

其中map映射爲/Kaptcha,如下所示在html頁面中設置驗證碼圖片的src爲…/Kaptcha

<div class="item-inner">
    <label for="kaptcha-code" class="item-title label">驗證碼</label>
    <input id="kaptcha-code" name="kaptcha-code" type="text"
            class="form-control in" placeholder="驗證碼"/>
    <div class="item-input">
        <img id="kaptcha_img" alt="點擊更換" title="點擊更換"
             onclick="changeCode(this)" src="../Kaptcha"/>
    </div>
</div>

點擊更換驗證碼即更改圖片的src,向/Kaptcha請求一個新的圖片,並且請求的參數附帶隨機數作爲參數

function changeCode(img) {
    img.src="../Kaptcha?"+Math.floor(Math.random()*100)
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章