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