该SSM项目的三个子系统:前端展示系统、店家管理系统、超级管理员系统。优先级应该为店家管理系统->前端展示系统->超级管理员系统,店家是最有可能带来收益的,而且只有店家管理添加信息,前端展示系统才有数据源,而超级管理员可以用手工录入的方式替代。接下来优先对店家管理系统开发。
店家管理系统有两个模块:店铺和商品。因为有店铺才有商品,所以我们先进行店铺的增删改查功能实现。
第一个目标,就是实现店铺信息的增删改查功能。首先就是店铺注册,要实现店铺注册就需要完成以下目标:
- 连接数据库
- Mybatis数据库表映射关系的配置
- dao -> service -> controller层代码的编写,Junit的使用
- Session,图片处理工具Thumbnailator的使用
- suimobile前端涉及与开发
首先在dao层建立ShopDao接口:
public interface ShopDao {
/**
* 新增店铺
*
* @param shop
* @return
*/
int insertShop(Shop shop);
}
由于是由mybatis来实现的,所以需要在resources/mapper先生成相应的ShopDao.xml配置文件:
<insert id="insertShop" useGeneratedKeys="true" keyColumn="shop_id"
keyProperty="shopId">
INSERT INTO
tb_shop(owner_id, area_id, shop_category_id,
shop_name, shop_desc, shop_addr,
phone, shop_img, priority,
create_time, last_edit_time, enable_status,
advice)
VALUES
(#{owner.userId},#{area.areaId},#{shopCategory.shopCategoryId},#{shopName},
#{shopDesc},#{shopAddr},#{phone},#{shopImg},#{priority},
#{createTime},#{lastEditTime}, #{enableStatus},#{advice})
</insert>
然后开始UT(单元)测试,在test/java/com/imooc/o2o/dao下创建ShopDaoTest测试类,
public class ShopDaoTest extends BaseTest {
@Autowired
private ShopDao shopDao;
@Test
public void testInsertShop() {
Shop shop = new Shop();
PersonInfo owner = new PersonInfo();
Area area = new Area();
ShopCategory shopCategory = new ShopCategory();
owner.setUserId(1L);
area.setAreaId(2);
shopCategory.setShopCategoryId(1L);
shop.setOwner(owner);
shop.setArea(area);
shop.setShopCategory(shopCategory);
shop.setShopName("测试的店铺");
shop.setShopDesc("test");
shop.setShopAddr("test");
shop.setPhone("test");
shop.setShopImg("test");
shop.setCreateTime(new Date());
shop.setEnableStatus(1);
shop.setAdvice("审核中");
int effectedNum = shopDao.insertShop(shop);
assertEquals(1, effectedNum);
}
}
然后运行,测试成功,该功能已完成。
接下来完成店铺更新模块,步骤同上,先在ShopDao接口中添加更新方法:
/**
* 更新店铺信息
* @param shop
* @return
*/
int updateShop(Shop shop);
然后写入相应的mapper配置,在ShopDao.xml配置文件中添加如下信息:
<update id="updateShop" parameterType="com.imooc.o2o.entity.Shop">
update tb_shop
<set>
<if test="shopName != null">shop_name=#{shopName},</if>
<if test="shopDesc != null">shop_desc=#{shopDesc},</if>
<if test="shopAddr != null">shop_addr=#{shopAddr},</if>
<if test="phone != null">phone=#{phone},</if>
<if test="shopImg != null">shop_img=#{shopImg},</if>
<if test="priority != null">priority=#{priority},</if>
<if test="lastEditTime != null">last_edit_time=#{lastEditTime},</if>
<if test="enableStatus != null">enable_status=#{enableStatus},</if>
<if test="advice != null">advice=#{advice},</if>
<if test="area != null">area_id=#{area.areaId},</if>
<if test="shopCategory != null">shop_category_id=#{shopCategory.shopCategoryId}
</if>
</set>
where shop_id=#{shopId}
</update>
然后添加测试类,在ShopDao测试类中,添加testUpdateShop()方法,由于我们的测试类中有上一个模块testInsertShop()方法,我们在testInsertShop方法上加上一个@Ignore注解,然后就可以正常进行下一个test方法的运行,
public void testUpdateShop() {
Shop shop = new Shop();
shop.setShopId(1L);
shop.setShopDesc("测试描述");
shop.setShopAddr("测试地址");
shop.setLastEditTime(new Date());
int effectedNum = shopDao.updateShop(shop);
assertEquals(1, effectedNum);
}
控制台显示测试成功,并且数据库相应数据已经改变,该模块基本功能完成。
接下来进行Thumbnailator类库的图片处理,首先在mvnrepository.com里下载相应的jar包,然后复制粘贴到项目的pom文件里,
<!-- 图片处理 -->
<!-- https://mvnrepository.com/artifact/net.coobird/thumbnailator -->
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.8</version>
</dependency>
然后就成功引入相关jar包。然后再util文件下创建ImageUtil.java工具类:
public class ImageUtil {
public static void main(String[] args) throws IOException {
String basePath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
Thumbnails.of(new File("D:\\work\\imooc\\image\\lanqiu.jpg")).size(200, 200)
.watermark(Positions.BOTTOM_RIGHT, ImageIO.read(new File(basePath + "/watermark.jpg")), 0.25f)
.outputQuality(0.8f).toFile("D:\\work\\imooc\\image\\lanqiunew.jpg");
}
}
然后我们在resource文件下添加watermark.jpg水印照片,然后运行上述方法,结果控制台报错:
显示无法读取文件,找了一下为中文乱码问题,再basePath变量下加上
basePath = URLDecoder.decode(basePath,"utf-8");
成功解决。效果为将本来读取的照片加上watermark水印。然后在util包下再一个获取图片路径的类,PathUtil.java:
public class PathUtil {
private static String seperator = System.getProperty("file.separator");
public static String getImgBasePath() {
String os = System.getProperty("os.name");
String basePath = "";
if(os.toLowerCase().startsWith("win")){
basePath = "D:/projectdev/image/";
}else {
basePath="/home/xiangze/image/";
}
basePath = basePath.replace("/",seperator);
return basePath;
}
public static String getShopImagePath(long shopId){
String imagePath = "/upload/item/shop/" + shopId +"/";
return imagePath;
}
}
其中的getImgBasePath()方法为获取图片路径,而getShopImagePath()为获取商品图片的路径,因为我们需要把商品图片放在各自的路径下。然后,在ImageUtil下写入其他方法,完善这个工具类,这里就不贴详细的代码了。
在进行service层的代码编写前,我们先准备好添加店铺的返回类型,我们在dto里添加这个类型。至于为什么不直接用Shop这个实体类来返回,那是因为我们在操作Shop时,会有一个状态(成功/失败),这些都是要记录和返回给controller层处理的。
所以我们在dto下创建ShopExecution.java类:
public class ShopExecution {
// 结果状态
private int state;
// 状态标识
private String stateInfo;
// 店铺数量
private int count;
// 操作的shop(增删改店铺的时候用到)
private Shop shop;
// shop列表(查询店铺列表的时候使用)
private List<Shop> shopList;
public ShopExecution() {
}
// 店铺操作失败的时候使用的构造器
public ShopExecution(ShopStateEnum stateEnum) {
this.state = stateEnum.getState();
this.stateInfo = stateEnum.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;
}
// 属性的get、set方法
// 。。。。。。
}
其中ShopStateEnum为商铺的状态,为一个枚举类,所以在enums下创建ShopStateEnum类,
public enum ShopStateEnum {
CHECK(0, "审核中"), OFFLINE(-1, "非法店铺"), SUCCESS(1, "操作成功"), PASS(2, "通过认证"), INNER_ERROR(-1001,
"内部系统错误"), NULL_SHOPID(-1002, "ShopId为空"),NULL_SHOP(-1003, "shop信息为空");
private int state;
private String stateInfo;
private ShopStateEnum(int state, String stateInfo) {
this.state = state;
this.stateInfo = stateInfo;
}
/**
* 依据传入的state返回相应的enum值
*/
public static ShopStateEnum stateOf(int state) {
for (ShopStateEnum stateEnum : values()) {
if (stateEnum.getState() == state) {
return stateEnum;
}
}
return null;
}
//只设置get方法,因为我们不希望有set方法改变状态值
public int getState() {
return state;
}
public String getStateInfo() {
return stateInfo;
}
}
这里在准备好基础类型后,我们开始进行正式的service层编码。首先,我们需要在src/main/java/com/imooc/o2o下创建exception包,用来存放ShopOperationException.java类,用来包装由于商铺操作出错而抛出的异常。
public class ShopOperationException extends RuntimeException{
//序列化
private static final long serialVersionUID = 2361446884822298905L;
public ShopOperationException(String msg) {
super(msg);
}
}
然后在service层下实现ShopService接口:
public interface ShopService {
/**
* 注册店铺信息,包括图片处理
*
* @param shop
* @param shopImgInputStream
* @param fileName
* @return
* @throws ShopOperationException
*/
ShopExecution addShop(Shop shop, InputStream shopImgInputStream, String fileName) throws ShopOperationException;
}
然后在service/impl下编写ShopServiceImpl.java的实现类,
@Service
public class ShopServiceImpl implements ShopService {
@Autowired
private ShopDao shopDao;
@Override
@Transactional
public ShopExecution addShop(Shop shop, InputStream shopImgInputSteam, String fileName)throws ShopOperationException{
// 空值判断
if (shop == null) {
return new ShopExecution(ShopStateEnum.NULL_SHOP);
}
try {
// 给店铺信息赋初始值
shop.setEnableStatus(0);
shop.setCreateTime(new Date());
shop.setLastEditTime(new Date());
// 添加店铺信息
int effectedNum = shopDao.insertShop(shop);
if (effectedNum <= 0) {
throw new ShopOperationException("店铺创建失败");
} else {
if (shopImgInputSteam != null) {
// 存储图片
try {
addShopImg(shop, shopImgInputSteam, fileName);
} catch (Exception e) {
throw new ShopOperationException("addShopImg error:" + e.getMessage());
}
// 更新店铺的图片地址
effectedNum = shopDao.updateShop(shop);
if (effectedNum <= 0) {
throw new ShopOperationException("更新图片地址失败");
}
}
}
} catch (Exception e) {
throw new ShopOperationException("addShop error:" + e.getMessage());
}
return new ShopExecution(ShopStateEnum.CHECK, shop);
}
private void addShopImg(Shop shop, InputStream shopImgInputStream, String fileName) {
// 获取shop图片目录的相对值路径
String dest = PathUtil.getShopImagePath(shop.getShopId());
String shopImgAddr = ImageUtil.generateThumbnail(shopImgInputStream, fileName, dest);
shop.setShopImg(shopImgAddr);
}
}
但是这里图片处理涉及到很多路径,应该很容易出错。接下来开始测试,在test/java/com/imooc/o2o/service下创建ShopServiceTest类,
public class ShopServiceTest extends BaseTest {
@Autowired
private ShopService shopService;
@Test
public void testAddShop() throws ShopOperationException, FileNotFoundException {
Shop shop = new Shop();
PersonInfo owner = new PersonInfo();
Area area = new Area();
ShopCategory shopCategory = new ShopCategory();
owner.setUserId(1L);
area.setAreaId(2);
shopCategory.setShopCategoryId(1L);
shop.setOwner(owner);
shop.setArea(area);
shop.setShopCategory(shopCategory);
shop.setShopName("测试的店铺3");
shop.setShopDesc("test3");
shop.setShopAddr("test3");
shop.setPhone("test3");
shop.setCreateTime(new Date());
shop.setEnableStatus(0);
shop.setAdvice("审核中");
File shopImg = new File("D:\\work\\imooc\\image\\lanqiu.jpg");
InputStream is = new FileInputStream(shopImg);
ShopExecution se = shopService.addShop(shop, is, shopImg.getName());
assertEquals(ShopStateEnum.CHECK.getState(), se.getState());
}
}
运行后果不其然出错了。
这个bug跟上个编写图片类的时候出现的bug是一样的,所以我们看一下是不是因为中文乱码的问题。 找不出这个bug的问题,但是本系列最主要是记录整体流程,如果能删除图片处理的就好了,可惜不能,说这个系列似乎要在这里夭折了。我想要尽快看完该项目,故没有太多时间去改bug同时去记录,所以后期用简短模式记录。
接下来是controller层,我们在web里创建shopadmin文件,在该文件下创建ShopManagementController.java,并且加入@Controller注释说明为controller层,然后用@RequestMapping指定访问路径,并且将请求封装成HttpServletRequest对象,我们在util包下创建HttpServletRequest.java。然后就可以完成controller层的设计。
接下来使用SUI mobile前端技术,在相关网站选择需要的组件,点击鼠标右键查看源码,就可以将相关代码复制到html文件中。我们在src/main/webapp/WEB-INF/html/shop下创建shopoperation.html文件,将源码复制过去即可。而该页面需要相关的css和js配置文件,所以我们可以在SUI mobile网站上找到CDN地址,替换掉源码中的css和js配置文件,就可以实现一个网站基本组件。
由于我们启动tomcat后无法直接从localhost:8080/o2o/WEB-INF/html/shop/shopoperation.html去直接访问这个页面,所以我们需要在controller层的shopadmin下创建一个ShopAdminController来完成一个注解,如下:
public class ShopAdminController {
@RequestMapping(value = "/shopoperation")
public String shopOperation() {
// 转发至店铺注册/编辑页面
return "shop/shopoperation";
}
}
为什么我们不用指定到html文件呢,因为我们在spring-web.xml配置时已经指定了前后缀,
然后再webapp文件下创建resources文件,并且创建js/shop文件,创建shopoperation.js文件,然后进行js配置文件的编写。
然后我们需要完成商铺类别的功能,从dao层,到resources的mapper文件下的mabatis映射,再到service层的接口和实现,然后controller层定义好路由。接下来的课程都为了商铺系统的某个功能从dao层一直往下开发的这么一个流程,所以我们直接跳过,来到缓存技术,开启下一章。