做完校园商铺平台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文件在最后设置一个
商铺编辑
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;
}
}