做完校園商鋪平臺O2O小項目1.0,項目來源慕課網,記錄一下平時遇到的問題🤔:
項目簡介:
項目1.0中使用SSM技術快速迭代出版校園商鋪1.0;同時包含MySQL主從同步實現讀寫分離,利用SUI Mobile快速實現響應式頁面,Redis緩存,數據庫加密配置,部署上線等實用技術點。
數據庫設計:
數據庫表的總體結構如下:
注意:
- 微信賬號和本地賬號是通過用戶信息表中的user_id進行關聯的,實現本地賬號和微信賬號的綁定。
- 店鋪信息表跟以下四個表的關聯:
- 商品信息表通過product_category_id跟商品類別表關聯,通過product_id跟詳情圖片表進行關聯
Logback日誌框架:
本項目中使用Logback日誌框架,Logback 是 Slf4j 的原生實現框架,同樣也是出自 Log4j 一個人之手,但擁有比 log4j 更多的優點、特性和更做強的性能,現在基本都用來代替 log4j 成爲主流。
Thumbnailator圖片處理:
GitHub鏈接地址
Thumbnailator是一個用來生成圖像縮略圖的 Java類庫,可生成圖片縮略圖,支持根據一個目錄批量生成縮略圖,支持圖片縮放,區域裁剪,水印,旋轉,保持比例等等。
注意:需要封裝工具類。
DTO及相關枚舉類:
Data Transfer Object,即數據傳送對象 。
DTO是一個普通的Java類,它封裝了要傳送的批量的數據。當客戶端需要讀取服務器端的數據的時候,服務器端將數據封裝在DTO中,這樣客戶端就可以在一個網絡調用中獲得它需要的所有數據。
Shop實體類包含了Shop的基本屬性,但是在前端操作時,我們希望可以返回操作的結果等信息,這個時候Shop實體類就不能滿足需求了,我們將操作結果和Shop等信息統一放到DTO中處理,即可滿足當前的需求。
DTO類ShopExecution:
package com.artisan.o2o.dto;
import java.util.List;
import com.artisan.o2o.entity.Shop;
import com.artisan.o2o.enums.ShopStateEnum;
/*
*DTO中還要包含操作商鋪的返回結果,單個的實體類無法滿足,所以封裝到dto中,便於操作
*/
public class ShopExecution {
private int state ;
private String stateInfo;
private int count;
private Shop shop;
/**
* 店鋪集合 (查詢店鋪列表的時候用)
*/
private List<Shop> shopList;
//構造函數,店鋪操作失敗的時候使用的構造函數
public ShopExecution(ShopStateEnum shopStateEnum) {
this.state = shopStateEnum.getState();
this.stateInfo = shopStateEnum.getStateInfo();
}
//構造函數,店鋪操作成功的時候使用的構造函數
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;
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
public String getStateInfo() {
return stateInfo;
}
public void setStateInfo(String stateInfo) {
this.stateInfo = stateInfo;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public Shop getShop() {
return shop;
}
public void setShop(Shop shop) {
this.shop = shop;
}
public List<Shop> getShopList() {
return shopList;
}
public void setShopList(List<Shop> shopList) {
this.shopList = shopList;
}
}
枚舉類:
使用枚舉表述常量數據字典。例如爲商品狀態定義一個枚舉類ShopStateEnum類。
package com.zzt.o2o.enums;
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;
private ShopStateEnum(int state, String stateInfo) {
this.state = state;
this.stateInfo = stateInfo;
}
public int getState() {
return state;
}
public String getStateInfo() {
return stateInfo;
}
// 定義換成pulic static 暴漏給外部,通過state獲取ShopStateEnum
public static ShopStateEnum stateOf(int state) {
for (ShopStateEnum stateEnum : values()) {
if(stateEnum.getState() == state){
return stateEnum;
}
}
return null;
}
}
驗證碼kaptcha組件:
GitHub地址:
https://github.com/penggle/kaptcha
Kaptcha是基於SimpleCaptcha的開源項目。通過調整Kaptcha配置可以生成各種樣式的驗證碼。
Kaptcha提供的功能如下:
-
驗證碼的字體
-
驗證碼字體的大小
-
驗證碼字體的字體顏色
-
驗證碼內容的範圍
-
驗證碼圖片的大小,邊框,邊框粗細,邊框顏色
-
驗證碼的干擾線
-
驗證碼的樣式
jackson的使用
使用jackson將前端傳過來的json數據格式轉換爲對應的pojo
GitHub:https://github.com/FasterXML/jackson-databind
商鋪註冊
設置圖片:我的數據庫中存儲的並不是全部路徑,而是部分路徑,因此在傳給前端路徑時並不完全,因此我們在tomcat的server.xml文件中配置,解決方案:
在idea不想es直接更改,打開tomca文件夾找到server.xml文件在最後設置一個,意思相當於當我們遇到第一個參數的路徑時轉第二個路徑就是當遇到/A就轉爲/A/B/C/這種
商鋪編輯
Sql中可將重複的sql提取出來,使用時用include引用即可,最終達到sql重用的目的
注意:如果引用其它mapper.xml的sql片段,則在引用時需要加上namespace,如下:<include refid="namespace.sql片段”/>
商鋪列表
列表頁面需要支持分頁 (MySql數據庫,我們使用limit關鍵字)
在dao層:分頁是這樣的:我們接受的是limit start,size (start從0開始),在mapper文件中只能指定相應的從某行到某行,
從前端卻只能傳來頁數和頁數大小,因此我們需要一個頁數轉爲記錄條數的記錄。
我們在Util類中寫入一個方法,參數接受頁數行頁數大小,通過將頁數減1乘上頁數大小,就是我們要索引的這一頁的所有記錄。
商品類別
在刪除商品類別時需要先將該商品目錄下的商品的類別Id置爲空,然後再刪除該商品目錄。
商品編輯
商品信息有商品縮略圖和詳情圖片,我們約定好:如果用戶傳入了新的商品縮略圖和詳情圖片,就將原有的商品縮略圖和詳情圖片刪除掉。
Redis使用:
加入緩存的配置步驟
- pom.xml 添加jedis依賴包
- redis配置文件
- spring-dao.xml加載redis.properties
- 封裝JedisPool,用於創建JedisPool
- 封裝操作redis的工具類 JedisUtil
- 新建spring-redis.xml 配置redis連接池和bean
- web.xml中加載spring-redis.xml
- Service層使用緩存
根據數據的特點,不經常變動的數據 即時性要求沒有那麼高的讀數據 爲了減輕DB壓力,我們可以將數據放到緩存中。
目前我們需要將區域信息、商鋪分類信息和頭條信息放入到redis中。因爲這些數據不怎麼變化。同時注意需要將這些數據分類存儲在redis中。
比如頭條廣告那裏有區分是否可以放上去,即狀態碼
商品分類那裏是否有子目錄。
對信息數據加密
使用 DES 對數據庫的用戶名和密碼進行加密。DES是一種對稱加密算法。
步驟:
- DES工具類
- 修改配置文件中即jdbc.properties 中的用戶名和密碼
- 繼承PropertyPlaceholderConfigurer,重寫convertProperty方法
- 配置自定義的EncryptPropertyPlaceholderConfigurer
使用 MD5 加密算法 對用戶信息進行加密。
SpringMVC 攔截器
- 對非法路徑進行攔截,確保一個店家只能在一個店鋪上進行商鋪編輯,商品管理等。
ShopPermissionInterceptor類:
package com.zzt.o2o.interceptor.shopadmin;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import com.zzt.o2o.entity.Shop;
/**
* 商家不能去操縱不屬於它管理的店鋪
* @author zhan
*
*/
public class ShopPermissionInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
Shop currentShop = (Shop) request.getSession().getAttribute(
"currentShop");
@SuppressWarnings("unchecked")
//從session中獲取當前用戶可操作的店鋪列表
List<Shop> shopList = (List<Shop>) request.getSession().getAttribute(
"shopList");
if (currentShop != null && shopList != null) {
for (Shop shop : shopList) {
//如果當前店鋪在可操作的列表則返回true,進行接下來的用戶操作
if (shop.getShopId() == currentShop.getShopId()) {
return true;
}
}
}
//不滿足返回false
return false;
}
}
- 如果路徑非法(沒有經過登錄)直接訪問路徑名,也將跳到登錄界面。
ShopLoginInterceptor類:
package com.zzt.o2o.interceptor.shopadmin;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import com.zzt.o2o.entity.PersonInfo;
/**
* 店家管理系統登錄驗證攔截器
* @author zhan
*
*/
public class ShopLoginInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
Object userObj = request.getSession().getAttribute("user");
if (userObj != null) {
PersonInfo user = (PersonInfo) userObj;
if (user != null && user.getUserId() != null
&& user.getUserId() > 0 && user.getEnableStatus() == 1)
return true;
}
//若不滿足登錄驗證,則直接跳轉到賬號登錄頁面
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<script>");
out.println("window.open ('" + request.getContextPath()+ "/local/login?usertype=2','_self')");
out.println("</script>");
out.println("</html>");
return false;
}
}